<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss'><id>tag:blogger.com,1999:blog-11049281</id><updated>2010-01-27T15:43:25.815-08:00</updated><title type='text'>Call Me Fishmeal.</title><subtitle type='html'>I write software. I'm obsessive-compulsive. I like good stuff. Kittens are fuzzy.</subtitle><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default'/><link rel='alternate' type='text/html' href='http://wilshipley.com/blog/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default?start-index=26&amp;max-results=25'/><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://wilshipley.com/blog/feed.xml'/><author><name>Wil Shipley</name><uri>http://www.blogger.com/profile/04429428424208378747</uri><email>noreply@blogger.com</email></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>134</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-11049281.post-2240640073652448426</id><published>2009-10-20T18:50:00.000-07:00</published><updated>2009-10-31T17:39:56.473-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='mac community'/><category scheme='http://www.blogger.com/atom/ns#' term='code'/><category scheme='http://www.blogger.com/atom/ns#' term='business'/><title type='text'>Pimp My Code, Part 17: Lost in Translations.</title><content type='html'>&lt;h3&gt;Introduction&lt;/h3&gt;If you’re developing commercial software, you’re going to want to sell it globally — for well-localized English-speaking software companies, for instance, I have seen between 20% and 25% of total revenue coming from non-English speaking countries. That’s, like, real money. You’ll want that.&lt;br /&gt;&lt;table align=center&gt;&lt;tr&gt;&lt;td&gt;&lt;table border=0 cellspacing=0 cellpadding=0&gt;&lt;tr&gt;&lt;th colspan=2&gt;Delicious Monster&lt;br&gt;&lt;u&gt;Gross Sales by&lt;br&gt;Language of Country&lt;/u&gt;&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;English&lt;/td&gt;&lt;td align=right&gt;80.9%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;German&lt;/td&gt;&lt;td align=right&gt;8.1%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;French&lt;/td&gt;&lt;td align=right&gt;3.2%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Japanese&lt;/td&gt;&lt;td align=right&gt;2.6%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Unknown / Other&lt;/td&gt;&lt;td align=right&gt;2.2%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Dutch&lt;/td&gt;&lt;td align=right&gt;1.5%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Spanish&lt;/td&gt;&lt;td align=right&gt;0.9%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Norwegian&lt;/td&gt;&lt;td align=right&gt;0.8%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Italian&lt;/td&gt;&lt;td align=right&gt;0.5%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Danish&lt;/td&gt;&lt;td align=right&gt;0.5%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Swedish&lt;/td&gt;&lt;td align=right&gt;0.5%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Chinese&lt;/td&gt;&lt;td align=right&gt;0.3%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Portuguese&lt;/td&gt;&lt;td align=right&gt;0.3%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan=2 align=right&gt;&lt;span style="font-style: italic; font-size: 5pt;"&gt;[Delicious Monster Oct09]&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/td&gt;&lt;td width=80&gt;&lt;/td&gt;&lt;td&gt;&lt;table border=0 cellspacing=0 cellpadding=0&gt;&lt;tr&gt;&lt;th colspan=2&gt;Omni Group&lt;br&gt;&lt;u&gt;Upgrades by Language&lt;/u&gt;&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;English&lt;/td&gt;&lt;td align=right&gt;74.4%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;German&lt;/td&gt;&lt;td align=right&gt;6.0%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;French&lt;/td&gt;&lt;td align=right&gt;5.3%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Japanese&lt;/td&gt;&lt;td align=right&gt;5.0%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Spanish&lt;/td&gt;&lt;td align=right&gt;2.3%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Other&lt;/td&gt;&lt;td align=right&gt;2.3%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Italian&lt;/td&gt;&lt;td align=right&gt;1.9%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Dutch&lt;/td&gt;&lt;td align=right&gt;1.4%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Chinese&lt;/td&gt;&lt;td align=right&gt;0.7%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Portuguese&lt;/td&gt;&lt;td align=right&gt;0.4%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Norwegian&lt;/td&gt;&lt;td align=right&gt;0.4%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan=2 align=right&gt;&lt;span style="font-style: italic; font-size: 5pt;"&gt;[&lt;a href="http://update.omnigroup.com/" target="_other"&gt;Omni Group&lt;/a&gt; Oct09]&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;br /&gt;Note that the data from the above tables was gathered in different ways: Omni tracks what language a user has set in her preferences &lt;em&gt;every time&lt;/em&gt; she upgrades any of her Omni software, whereas I tracked Delicious Monster’s &lt;em&gt;total dollar sales&lt;/em&gt; by country, and then assigned each country a “primary” language. (For countries like Switzerland, obviously my approach is an approximation.)&lt;br /&gt;&lt;br /&gt;Still, you can try to make some educated guesses from these two data sets, although obviously correlations aren’t necessarily causative when analyzing only two samples. But, for example, Omni has Japanese-language support and Japanese-only boxed versions of their products; their Japanese number is twice that of Delicious Monster, which doesn’t have a partnership like this (yet). On the flip side, I have a Norwegian translation in Delicious Library 2, whereas OmniGraffle does not; my Norwegian number is twice that of Omni’s.&lt;br /&gt;&lt;br /&gt;My German number is a bit higher, which is surprising because in general Delicious Library doesn’t appear to have penetrated overseas as well as Omni has. Maybe Germans love to organize books? Maybe they love doodads, and they bought my optional Bluetooth scanner, which makes their gross sales number much higher? There’s clearly a whole post to be written about mining this data, but this isn’t that post.&lt;br /&gt;&lt;h3&gt;Abstract&lt;/h3&gt;In this post I’m going to explain to you what internationalization and localization are, how Apple’s tools handle them by default, and the huge flaws in Apple’s approach. Then I’m going to provide you with the code and tools to do localization in a much, much easier way.&lt;br /&gt;&lt;br /&gt;Then you’re going to think, “That will never work, because of blah!” and I’m going to respond, &lt;em&gt;as if I can read your mind or I’ve already had this argument with a dozen developers,&lt;/em&gt; “It already did — I used these tools in Delicious Library and &lt;a href="delicious-monster.com" target="other"&gt;Delicious Library 2&lt;/a&gt; and they've won three Apple Design Awards between them.”&lt;br /&gt;&lt;br /&gt;And you’re going to think, “Stupid show-off… why does he keep mentioning those?” And I’m going to say, “Look, in this case I think it’s justified, because the Design Awards are given based largely on the interface of an application, and I’m merely trying to demonstrate that the compromises I had to make in interface design didn’t have a huge impact on the feel of…” and then we’ll go back and forth for a while in an imaginary argument while I try to convince you to do things my way, dammit, because I’m the daddy.&lt;br /&gt;&lt;h3&gt;“Internationalization” vs. “Localization”&lt;/h3&gt;What is internationalization, and what is localization? Good question! I’m so glad you asked.&lt;br /&gt;&lt;br /&gt;First, you should really read &lt;a href="http://developer.apple.com/internationalization/" target="other"&gt;Apple’s documentation&lt;/a&gt; on it. I mean, seriously, this should ALWAYS be your first target of inquiry. Did you read it yet? How about now? Now?&lt;br /&gt;&lt;br /&gt;Ok, fine… briefly, ‘internationalization’ is getting your app &lt;em&gt;ready&lt;/em&gt; to be localized for different languages/countries, and ‘localization’ is the process of actually adding a particular language to your app. The two are obviously are very closely related (you can't localize until you internationalize, and there's no point in internationalizing if you're not going to actually do any localizations) but it's vital to understand they are two distinct steps.&lt;br /&gt;&lt;br /&gt;For instance, as a developer, you don't need to speak German to get a German localization — you just need to do the internationalization part and find one friendly deutschophone to do the localization, and you're golden. (I mean &lt;em&gt;literally&lt;/em&gt; golden, as in covered in gold — don't ignore the German market! It's 8% of my total sales!)&lt;br /&gt;&lt;h3&gt;Apple's Localization Process&lt;/h3&gt;Ok, you’re sold on localization. But how do you proceed? Another good question! (Again, &lt;strong&gt;thank you&lt;/strong&gt; for tossing me all these softballs.)&lt;br /&gt;&lt;br /&gt;First off (as I’ve said many-a-time), when you are writing your source code you should surround EVERY string that the user might eventually see in the &lt;a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Functions/Reference/reference.html#//apple_ref/c/func/NSLocalizedString" target="other"&gt;NSLocalizedString()&lt;/a&gt; macro (or a variant). This will enable you to later automatically pull the English phrases out of your app, for translation by people who actually, like, speak other languages. Errrr… whatchacallums. “Polyglots!”&lt;br /&gt;&lt;br /&gt;But then what? Apple’s recommended process is:&lt;ul&gt;&lt;li&gt;You run Apple’s command-line tool, ‘genstrings’, over your source files (hopefully as part of your build process in Xcode), to pull out all the English phrases you marked with NSLocalizedString() and its cousins. See, I told you that’d be useful! And here’s it’s already paid off, only a couple paragraphs later!&lt;br /&gt;&lt;li&gt;You give the “.strings” files that genstrings generates, along with any XIBs and image files that contain English words, to a localizer, who is a &lt;em&gt;native speaker&lt;/em&gt; of the language you want.&lt;br /&gt;&lt;li&gt;The localizer creates a new directory of translated “.strings” files, plus XIBs and images with English words translated as well.&lt;br /&gt;&lt;li&gt;You take this directory, and put in into a language bundle in your project, like “ja.lproj”.&lt;/ul&gt;Easy? Sort of. But you can already see problems:&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Problem 1: Localizers Can’t Effectively Localize Images&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;You really &lt;em&gt;really&lt;/em&gt; don’t want your localizers editing your image files. I mean, your artists spent a lot of time making your images really nice (since you’re &lt;em&gt;not&lt;/em&gt; an idiot and you didn’t try to do your artwork yourself) and they used things like &lt;em&gt;layers&lt;/em&gt; and &lt;em&gt;paths&lt;/em&gt; and &lt;em&gt;filters&lt;/em&gt; and stuff, and your localizers don’t know how to use Photoshop and aren’t artists, and furthermore you probably gave your localizers the final PNG or JPEG2000 files instead of PSDs and so they don’t have all those layers and paths and filters and they’d do a really crappy job if they even could do it which they can’t.&lt;br /&gt;&lt;br /&gt;Also, your artists are going to keep tweaking the images right up until the day you ship (and after — and then they’re going to beg you to delay) and you could spend 100% of your time just sending out updated images to localizers for re-translation, and your localizers are either going to charge you a fortune or get pissed off and quit (depending on if you pay them or not, respectively).&lt;br /&gt;&lt;br /&gt;&lt;em&gt;Luckily,&lt;/em&gt; there’s a simple and obvious solution to this one: DO NOT EVER PUT WORDS IN YOUR IMAGES.&lt;br /&gt;&lt;br /&gt;&lt;div style="float:right;"&gt;&lt;img src="http://lh6.ggpht.com/_phmvWzdPAMk/StvUU6h_mkI/AAAAAAAAAB8/9VBk4mMvKR0/buyalbum.png?imgmax=800" alt="buyalbum.png" border="0" width="204" height="13" /&gt;&lt;br&gt;&lt;img src="http://lh6.ggpht.com/_phmvWzdPAMk/StvbG4xjVkI/AAAAAAAAACI/luVWGoYYJfc/buybook.png?imgmax=800" alt="buybook.png" border="0" width="204" height="13" /&gt;&lt;br&gt;&lt;img src="http://lh6.ggpht.com/_phmvWzdPAMk/StvaVyW7_UI/AAAAAAAAACE/lInP9z1G-eg/buysong.png?imgmax=800" alt="buysong.png" border="0" width="204" height="13" /&gt;&lt;br&gt;&lt;center&gt;&lt;strong&gt;this is wrong, wrong, so wrong.&lt;/strong&gt;&lt;/center&gt;&lt;br&gt;&lt;/div&gt;Yes, I KNOW Apple does it. I think it’s some bizarre hold-over from their Carbon days, when drawing &lt;em&gt;actual styled text&lt;/em&gt; on a 3D button using &lt;em&gt;actual source code&lt;/em&gt; was CRAZY TALK. Plus Apple has a huge in-house art staff, so it’s easy for engineers to say, “Hey, art dudes, give me a button in three different states that says, ‘Buy Album’ in our latest iTunes-only-style-that’s-not-quite-like-Cocoa’s-standards,” and the engineer is done for the day and didn’t have to write any code and can go suck down a mojito.&lt;br /&gt;&lt;br /&gt;Never mind that every time Apple’s (separate-but-equal) iTunes UI team changes the look of their buttons, the artists have to redo several &lt;em&gt;hundred&lt;/em&gt; images and then re-localize them, resulting in &lt;em&gt;thousands&lt;/em&gt; of fiddly images. That’s certainly a lot more efficient than the programmer spending a couple hours writing code to draw the button border and inside and the shadow on the text, &lt;em&gt;once,&lt;/em&gt; and then just modifying that code in &lt;em&gt;one place&lt;/em&gt; whenever the look changes, right? RIGHT? (&lt;super&gt;*&lt;/super&gt; see answer at end of post.)&lt;br /&gt;&lt;br /&gt;Luckily, you don’t even have the option to do it this stupid way, because you don’t have an art department whose time you’d like to waste. So, use icons whenever possible on buttons, and if you &lt;em&gt;must&lt;/em&gt; use text, then make it localizable. Luckily, this is pretty easy in Cocoa, since the button edges, insides, and the various shines and shadows are &lt;em&gt;all done for you&lt;/em&gt; if you use the standard mechanisms, and they’re easy to subclass, once, if you want a custom look.&lt;br /&gt;&lt;br /&gt;For button titles that require text, you can either type English words directly into Interface Builder, or if you’re not on an iPhone you can bind the button’s title some method in your code and use NSLocalizedString() to make sure it’s localized. Since you’re going to have to localize most of your XIBs anyhow (because of menu items, mouseover help text, explanatory text boxes, field titles, window names, etc.), I really only recommend the binding approach if your button’s title changes in a way you can’t model in Interface Builder itself. (That is, don’t write an extra method if you don’t have to. Less code is better.)&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Problem 2: Localizers Can’t Effectively Localize XIBs&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;Asking your localizers to modify XIBs directly (using Interface Builder) is a huge pain: First, you limit your pool of potential localizers if they have to have the developer tools installed AND understand how to use them.&lt;br /&gt;&lt;br /&gt;&lt;div style="float:right;"&gt;&lt;img src="http://lh3.ggpht.com/_phmvWzdPAMk/St1ubXr8kcI/AAAAAAAAACQ/UgOzjGyUa4E/inspectorforlocalization.png?imgmax=800" alt="inspectorforlocalization.png" border="0" width="241" height="364" /&gt;&lt;br&gt;&lt;center&gt;&lt;strong&gt;hope the localizer finds this "display pattern!"&lt;/strong&gt;&lt;/center&gt;&lt;br&gt;&lt;/div&gt;Second, XIB files aren’t flat or self-documenting, so it’s VERY hard for localizers to find EVERY last English word in your XIB. Did they forget a tooltip? What about an alternate title for a button? What about a button’s title binding’s formatter string? Maybe there’s a hidden view somewhere? You’ll have to keep testing the localizations you get for complete coverage, and sending them back to be redone, and then re-testing them again. And again. Yuck.&lt;br /&gt;&lt;br /&gt;Sure, there’s a cool command in Interface Builder now, where you can hit control-S and see all the strings in your interface — but that’s ANOTHER thing you have to teach &lt;em&gt;each&lt;/em&gt; localizer to do. In addition to knowing how to use Interface Builder in the first place.&lt;br /&gt;&lt;br /&gt;Third, XIBs are like source code: they are written by programmers and contain functional parts. If your localizers happen to delete a button, or disconnect a binding, &lt;em&gt;your program stops working for that language.&lt;/em&gt; Remember, your localizers are NOT coders — they don’t have the same innate fear of changing XIBs that you’ve learned from years of boning yourself. And how fun is it to debug a program that works differently in different languages? Not fun.&lt;br /&gt;&lt;br /&gt;Fourth, many language are not as compact as English: the French and Germans are particularly fond of using the descriptions with the lots of the words or compoundwordstodescribeasingleconcept, respectively. You’re asking your localizers to resize your buttons and titles, and then, if those don’t fit in their containers, resize your views and widgets and panels as well.&lt;br /&gt;&lt;br /&gt;Let me restate that last point: &lt;em&gt;you are asking your localizers to design your interface.&lt;/em&gt; If you’re making an application that catalogs users’ books, CDs, DVDs, and other physical media, then I urge you to go ahead and do this. For the rest of you, WHAT THE HELL ARE YOU THINKING?&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Problem 3: Localizers Can’t Localize from Only Your App&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;In the old days, the NIBs we shipped were the same as the NIBs we used to build the app (and to localize), so any polyglot user in the world could just make a new language folder in our app wrapper by copying our English resources, and then start localizing the NIBs and .strings files, and at any time she could launch our app and check her progress. (Which is good, because it’s AMAZINGLY common for localizers to screw up the punctuation in .strings files, and Cocoa’s localization machinery will just silently ignore all strings after any punctuation mistake, resulting in mystery partial localizations.)&lt;br /&gt;&lt;br /&gt;Sure, the user had to understand Interface Builder, but at least if she did, she had everything she needed.&lt;br /&gt;&lt;br /&gt;Nowadays, Apple doesn’t want people mucking about CHANGING their programs, so we compile XIBs down into read-only NIBs, which means we have to give localizers our original XIB files (as well as our .strings) before they can start work.&lt;br /&gt;&lt;br /&gt;Think about the difference between some user, somewhere, just deciding to start editing a folder full of files that she already has, versus her having to write you, you having to bundle up all your XIBs and .strings and send them to her, her having to edit them all without seeing ANY results while she is doing it, then bundle them up and send them back to you, then you have to integrate them into your build system and make a test release, and either look at it yourself or send it to her again.&lt;br /&gt;&lt;br /&gt;Lather, rinse, repeat, lather, stab, stab, stab.&lt;br /&gt;&lt;br /&gt;Even if this &lt;em&gt;weren’t&lt;/em&gt; a horrible, time-wasting process (it is), this points to the final and biggest problem with localization:&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Problem 4: You Have to Maintain Multiple Copies of Each Localized Resource&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;If you have nine translations and each has a localized copy of every XIB in your application, then you’ll have to manually maintain nine different versions of each XIB. The strings in XIBs don’t change super-often, but the connections, flags, layout, and other object properties change all the time. And every time you change a flag or layout or connection in one XIB, you have to do the same thing eight more times. And do it perfectly, or you’ll have a language-specific bug.&lt;br /&gt;&lt;br /&gt;Of course, you can wait until all your XIBs are fully finished before you localize, but, if you’re like me, you are still fiddling with your XIBs until the day you ship your 1.0 code. So you’d have wait to localize until AFTER shipping 1.0, which stinks, or put off shipping until you’ve finished the product, sent out the localizations, AND gotten them all back — which stinks. And then you find have to change some buggy XIBs for your version 1.2, and you’d still have the exact same problem of having to edit nine XIBs to make one change.&lt;br /&gt;&lt;br /&gt;(This is obviously also true of images that have been localized, but hopefully I’ve talked you out of EVER doing that, so I’m not going to discuss that further.)&lt;br /&gt;&lt;br /&gt;Now, Apple has provided some tools in an attempt to make this all easier — they have something called “ibtool” (“nibtool” before Leopard) which can pull all the strings from a file, and then put new ones in their place, supposedly. In fact, it’s intended to allow you to look at the geometry and language changes between two localized versions of the same XIB, so you can generate, say, a Japanese version of your Main Menu nib with the latest changes from your English Main Menu nib, except keeping changes that you specifically made in the geometry of objects in the German version (since some words are longer and/or shorter in other languages, and thus XIBs sometimes require relayout).&lt;br /&gt;&lt;br /&gt;Apple also has a tool called &lt;a href="http://developer.apple.com/internationalization/localization/tools.html" target="_other"&gt;AppleGlot&lt;/a&gt; which sets up an entire “translation environment.” It apparently dates from Carbon days, and although it’s been updated to work with some versions of Interface Builder last time I tried it, it was a lot more trouble than value to me.&lt;br /&gt;&lt;br /&gt;Finally, there are some third-party tools that attempt to simplify some of this: for example &lt;a href="http://www.sticksoftware.com/software/Polyglot.html" target="_other" rel="nofollow"&gt;Polyglot&lt;/a&gt;, but it appears to have not been updated in a while, and I’m honestly not sure what all it does. (I’m providing the link so you can research it and other products if you want.)&lt;br /&gt;&lt;br /&gt;I disagree with the entire approach of storing a bunch of geometry diffs to your XIBs — this seems INCREDIBLY fragile to me. What happens when you move a text field from one view to another? What happens if you even move views around? All those geometry changes turn to gooblity-gook.&lt;br /&gt;&lt;br /&gt;The fundamental problem with every tool I’ve seen to fix your XIBs, however,is that &lt;u&gt;you&lt;/u&gt; still have to maintain all the localized versions. &lt;em&gt;You&lt;/em&gt; have nine &lt;u&gt;extra&lt;/u&gt; copies of each XIB in your source code, &lt;em&gt;you&lt;/em&gt; have to make sure they are all synced up, somehow. &lt;br /&gt;&lt;br /&gt;Now, if I told you, “Look, it’s easy to localize your Objective-C files — just maintain ten copies of each! Every time you change one, you’ll have to change all the others… but don’t worry! I’ve provided some tools that kind of help with this… until they don’t…” What would you think?&lt;br /&gt;&lt;br /&gt;You’d spit in my eye. And I don’t like spitty eyes. So, I came up with a better way, with exactly the same solution Apple used for Objective-C files.&lt;br /&gt;&lt;h3&gt;An Almost Ideal Solution&lt;/h3&gt;Let’s design, in our heads, the perfect localization system, or as close as we can get without, you know, having to do a lot of work:&lt;br /&gt;&lt;br /&gt;• First, we simply promise ourselves we won’t put words in image files. There, that solves a lot. This is usually Apple’s convention as well, although even certain Cocoa teams (-cough-iWeb-cough-) have used fully-rendered images of words like “PUBLISH” instead of using strings — hey, THAT’S not going to be resolution-independent, is it? (I keeed! I keeeeeeed!)&lt;br /&gt;&lt;br /&gt;• For XIBS, what we’d like (as programmers) is to have and maintain ONE single English XIB, which will polymorph to the user’s chosen language &lt;i&gt;at launch time&lt;/i&gt;, so we don’t have to ship with ten versions of the same XIB (they’re surprisingly big) or maintain ten versions of the same XIB in our source code (with the concomitant increase in errors as our XIB localizations slowly drift out of sync).&lt;br /&gt;&lt;br /&gt;• If we (the programmers) change a string or the whole look of a XIB, we want partial localizations to still work — it’s fine with with us if sometimes we ship with one or two English words “peeking through” a localization, since in most countries (obviously not France) they mix in English words all the time anyhow. It’s certainly better to have a partial localization (as long as it is of high-quality) than either no localization or a corrupted one. (This goes against a programmer’s natural inclination to insist that solutions be provably perfect. Get used to it.)&lt;br /&gt;&lt;br /&gt;• To make our localizers’ jobs easier, what we’d like to do is give them ONLY .strings files, so all localization takes is any text editor – localizers don’t have to know how to use Interface Builder or any developer tools.&lt;br /&gt;&lt;br /&gt;• We’re forgetful, so we want to generate the strings files from our XIBs and source files every time we do a build, so we never give out-of-date versions to our localizers — if they have a build of the app, then they have the latest strings files, AND the means to test their localizations themselves.&lt;br /&gt;&lt;br /&gt;• Since our app is going to ship with the complete set of .strings files needed to localize it, ANY customer who speaks another language can copy our English.lproj to TheirLanguage.lproj, edit the .strings files, and voila create a localized version of our app after a couple hours’ work.&lt;br /&gt;&lt;h3&gt;Here's the Code&lt;/h3&gt;Now, as you might guess, I've already done this, and, if I may plug myself for a second, this code was already available to clients of &lt;a href="http://golden-braeburn.com" target="_other"&gt;Golden % Braeburn&lt;/a&gt;, which is my other company where I give my Delicious store source code (and other miscellaneous helpful code) to Mac developers for a paltry percentage of sales. (Yes, Golden % Braeburn has launched, and yes, one of Golden % Braeburn's clients, Acacia Tree Software, is &lt;a href="http://acaciatreesoftware.com/" target="_other"&gt;currently selling its app&lt;/a&gt; with our store.)&lt;br /&gt;&lt;br /&gt;The first step to slurp all the English strings out of your XIB files at build time, so your localizers have nice flat strings files to work with. Add two new build phases to your application target in Xcode using “Project ▶ New Build Phase ▶ New Run Script Build Phase”, set the shells to &lt;code&gt;/bin/zsh&lt;/code&gt;, and have them look like this:&lt;br /&gt;&lt;br /&gt;&lt;table class="codeTable"&gt;&lt;tr&gt;&lt;td class="codeHeader"&gt;Internationalize Source Code Shell Script Build Phase&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td  class="codeCell"&gt;&lt;br /&gt;# -q silences duplicate comments with same key warning&lt;br /&gt;genstrings -q -o ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/${DEVELOPMENT_LANGUAGE}.lproj ${SRCROOT}/**/*.[hm]&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;table class="codeTable"&gt;&lt;tr&gt;&lt;td class="codeHeader"&gt;Internationalize XIBs Shell Script Build Phase&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td  class="codeCell"&gt;&lt;br /&gt;foreach nibFile (${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/**/*.nib)&lt;br /&gt; stringsFilePath=${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/${DEVELOPMENT_LANGUAGE}.lproj/`basename ${nibFile} .nib`.strings&lt;br /&gt; xibFile=`basename ${nibFile} .nib`.xib&lt;br /&gt; xibFilePath=`echo ${SOURCE_ROOT}/**/${xibFile}`&lt;br /&gt; if [[ -e ${xibFilePath} ]] {&lt;br /&gt;  ibtool --generate-stringsfile ${stringsFilePath}~ ${xibFilePath}&lt;br /&gt;  ${BUILT_PRODUCTS_DIR}/xibLocalizationPostprocessor ${stringsFilePath}~ ${stringsFilePath}&lt;br /&gt;  rm ${stringsFilePath}~&lt;br /&gt; }&lt;br /&gt;end&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;br /&gt;&lt;br /&gt;Now comes a slight hitch — under Leopard and beyond, the “ibtool” command &lt;em&gt;idiotically&lt;/em&gt; outputs a new format of .strings file that looks like this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;/* Class = "NSButtonCell"; title = "Get iPhone &amp; iPod Touch App";&lt;br /&gt;                     ObjectID = "1401603"; */&lt;br /&gt;"1401603.title" = "Get iPhone &amp; iPod Touch App";&lt;br /&gt;&lt;br /&gt;/* Class = "NSTextFieldCell"; title = "Remote Libraries:";&lt;br /&gt;                     ObjectID = "1401604"; */&lt;br /&gt;"1401604.title" = "Remote Libraries:";&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Instead of the standard file format (output by the older “nibtool” and the current “genstrings”):&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;/* Class = "NSButtonCell"; title = "Get iPhone &amp; iPod Touch App";&lt;br /&gt;                     ObjectID = "1401603"; */&lt;br /&gt;"Get iPhone &amp; iPod Touch App" = "Get iPhone &amp; iPod Touch App";&lt;br /&gt;&lt;br /&gt;/* Class = "NSTextFieldCell"; title = "Remote Libraries:";&lt;br /&gt;                     ObjectID = "1401604"; */&lt;br /&gt;"Remote Libraries:" = "Remote Libraries:";&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This new format completely violates the whole .strings file paradigm and obviously makes it impossible for us to use this to localize XIBs on-the-fly, because when we load XIBs on our own, &lt;em&gt;we don't have those ObjectIDs&lt;/em&gt; so we can't match objects up to the strings they need. Nice going, Apple.&lt;br /&gt;&lt;br /&gt;So, I wrote a little program to post-process “ibtool”s output to make it like “nibtool” and “genstrings”, you should add this code to your Xcode project and add a new “tool” target for it and make sure your main app depends on this tool being built, and all will be well:&lt;table class="codeTable"&gt;&lt;tr&gt;&lt;td class="codeHeader"&gt;xibLocalizationPostprocessor.m: Post-processing ibtool output&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td  class="codeCell"&gt;&lt;span style="color: #008b00;"&gt;//&lt;span class="Apple-converted-space"&gt;  &lt;/span&gt;xibLocalizationPostprocessor.m&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #008b00;"&gt;//&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #008b00;"&gt;//&lt;span class="Apple-converted-space"&gt;  &lt;/span&gt;Created by William Shipley on 4/14/08.&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #008b00;"&gt;//&lt;span class="Apple-converted-space"&gt;  &lt;/span&gt;Copyright © 2005-2009 Golden % Braeburn, LLC.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: #e50000;"&gt;&lt;span style="color: #814726;"&gt;#import &lt;/span&gt;&amp;lt;Cocoa/Cocoa.h&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span style="color: #cd00a3;"&gt;int&lt;/span&gt; main(&lt;span style="color: #cd00a3;"&gt;int&lt;/span&gt; argc, &lt;span style="color: #cd00a3;"&gt;const&lt;/span&gt; &lt;span style="color: #cd00a3;"&gt;char&lt;/span&gt; *argv[])&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #7925ac;"&gt;&lt;span style="color: #000000;"&gt;&lt;span class="Apple-converted-space"&gt;    &lt;/span&gt;&lt;/span&gt;NSAutoreleasePool&lt;span style="color: #000000;"&gt; *autoreleasePool = [[&lt;/span&gt;NSAutoreleasePool&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #430083;"&gt;alloc&lt;/span&gt;&lt;span style="color: #000000;"&gt;] &lt;/span&gt;&lt;span style="color: #205a5e;"&gt;init&lt;/span&gt;&lt;span style="color: #000000;"&gt;]; {&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;if&lt;/span&gt; (argc != &lt;span style="color: #2700dc;"&gt;3&lt;/span&gt;) {&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #e50000;"&gt;&lt;span style="color: #000000;"&gt;&lt;span class="Apple-converted-space"&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span style="color: #430083;"&gt;fprintf&lt;/span&gt;&lt;span style="color: #000000;"&gt;(&lt;/span&gt;&lt;span style="color: #814726;"&gt;stderr&lt;/span&gt;&lt;span style="color: #000000;"&gt;, &lt;/span&gt;"Usage: %s inputfile outputfile\n"&lt;span style="color: #000000;"&gt;, argv[&lt;/span&gt;&lt;span style="color: #2700dc;"&gt;0&lt;/span&gt;&lt;span style="color: #000000;"&gt;]);&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;            &lt;/span&gt;&lt;span style="color: #430083;"&gt;exit&lt;/span&gt; (-&lt;span style="color: #2700dc;"&gt;1&lt;/span&gt;);&lt;span class="Apple-converted-space"&gt;   &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSError&lt;/span&gt; *error = &lt;span style="color: #cd00a3;"&gt;nil&lt;/span&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSStringEncoding&lt;/span&gt; usedEncoding;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSString&lt;/span&gt; *rawXIBStrings = [&lt;span style="color: #7925ac;"&gt;NSString&lt;/span&gt; &lt;span style="color: #430083;"&gt;stringWithContentsOfFile&lt;/span&gt;:[&lt;span style="color: #7925ac;"&gt;NSString&lt;/span&gt; &lt;span style="color: #430083;"&gt;stringWithUTF8String&lt;/span&gt;:argv[&lt;span style="color: #2700dc;"&gt;1&lt;/span&gt;]] &lt;span style="color: #430083;"&gt;usedEncoding&lt;/span&gt;:&amp;amp;usedEncoding &lt;span style="color: #430083;"&gt;error&lt;/span&gt;:&amp;amp;error];&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;if&lt;/span&gt; (error) {&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;            &lt;/span&gt;&lt;span style="color: #430083;"&gt;fprintf&lt;/span&gt;(&lt;span style="color: #814726;"&gt;stderr&lt;/span&gt;, &lt;span style="color: #e50000;"&gt;"Error reading %s: %s\n"&lt;/span&gt;, argv[&lt;span style="color: #2700dc;"&gt;1&lt;/span&gt;], error.localizedDescription.UTF8String);&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;            &lt;/span&gt;&lt;span style="color: #430083;"&gt;exit&lt;/span&gt; (-&lt;span style="color: #2700dc;"&gt;1&lt;/span&gt;);&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class="p2"&gt;&lt;span class="Apple-converted-space"&gt;                                   &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSMutableString&lt;/span&gt; *outputStrings = [&lt;span style="color: #7925ac;"&gt;NSMutableString&lt;/span&gt; &lt;span style="color: #430083;"&gt;string&lt;/span&gt;];&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSUInteger&lt;/span&gt; lineCount = &lt;span style="color: #2700dc;"&gt;0&lt;/span&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSString&lt;/span&gt; *lastComment = &lt;span style="color: #cd00a3;"&gt;nil&lt;/span&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;for&lt;/span&gt; (&lt;span style="color: #7925ac;"&gt;NSString&lt;/span&gt; *line &lt;span style="color: #cd00a3;"&gt;in&lt;/span&gt; [rawXIBStrings &lt;span style="color: #430083;"&gt;componentsSeparatedByString&lt;/span&gt;:&lt;span style="color: #e50000;"&gt;@"\n"&lt;/span&gt;]) {&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;            &lt;/span&gt;lineCount++;&lt;/span&gt;&lt;br /&gt;&lt;span class="p2"&gt;&lt;span class="Apple-converted-space"&gt;            &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #008b00;"&gt;&lt;span style="color: #000000;"&gt;&lt;span class="Apple-converted-space"&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;if&lt;/span&gt;&lt;span style="color: #000000;"&gt; ([line &lt;/span&gt;&lt;span style="color: #430083;"&gt;hasPrefix&lt;/span&gt;&lt;span style="color: #000000;"&gt;:&lt;/span&gt;&lt;span style="color: #e50000;"&gt;@"/*"&lt;/span&gt;&lt;span style="color: #000000;"&gt;]) { &lt;/span&gt;// eg: /* Class = "NSMenuItem"; title = "Quit Library"; ObjectID = "136"; */&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;lastComment = line;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;continue&lt;/span&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;            &lt;/span&gt;} &lt;span style="color: #cd00a3;"&gt;else&lt;/span&gt; &lt;span style="color: #cd00a3;"&gt;if&lt;/span&gt; (line.&lt;span style="color: #7925ac;"&gt;length&lt;/span&gt; == &lt;span style="color: #2700dc;"&gt;0&lt;/span&gt;) {&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;lastComment = &lt;span style="color: #cd00a3;"&gt;nil&lt;/span&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;continue&lt;/span&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;            &lt;/span&gt;} &lt;span style="color: #cd00a3;"&gt;else&lt;/span&gt; &lt;span style="color: #cd00a3;"&gt;if&lt;/span&gt; ([line &lt;span style="color: #430083;"&gt;hasPrefix&lt;/span&gt;:&lt;span style="color: #e50000;"&gt;@"\""&lt;/span&gt;] &amp;amp;&amp;amp; [line &lt;span style="color: #430083;"&gt;hasSuffix&lt;/span&gt;:&lt;span style="color: #e50000;"&gt;@"\";"&lt;/span&gt;]) { &lt;span style="color: #008b00;"&gt;// eg: "136.title" = "Quit Library";&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p2"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSRange&lt;/span&gt; quoteEqualsQuoteRange = [line &lt;span style="color: #430083;"&gt;rangeOfString&lt;/span&gt;:&lt;span style="color: #e50000;"&gt;@"\" = \""&lt;/span&gt;];&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;if&lt;/span&gt; (quoteEqualsQuoteRange.&lt;span style="color: #7925ac;"&gt;length&lt;/span&gt; &amp;amp;&amp;amp; &lt;span style="color: #430083;"&gt;NSMaxRange&lt;/span&gt;(quoteEqualsQuoteRange) &amp;lt; line.&lt;span style="color: #7925ac;"&gt;length&lt;/span&gt; - &lt;span style="color: #2700dc;"&gt;1&lt;/span&gt;) {&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                    &lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;if&lt;/span&gt; (lastComment) {&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                        &lt;/span&gt;[outputStrings &lt;span style="color: #430083;"&gt;appendString&lt;/span&gt;:lastComment];&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                        &lt;/span&gt;[outputStrings &lt;span style="color: #430083;"&gt;appendString&lt;/span&gt;:&lt;span style="color: #e50000;"&gt;@"\n"&lt;/span&gt;];&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                    &lt;/span&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                    &lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSString&lt;/span&gt; *stringNeedingLocalization = [line &lt;span style="color: #430083;"&gt;substringFromIndex&lt;/span&gt;:&lt;span style="color: #430083;"&gt;NSMaxRange&lt;/span&gt;(quoteEqualsQuoteRange)]; &lt;span style="color: #008b00;"&gt;// chop off leading: "blah" = "&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                    &lt;/span&gt;stringNeedingLocalization = [stringNeedingLocalization &lt;span style="color: #430083;"&gt;substringToIndex&lt;/span&gt;:stringNeedingLocalization.&lt;span style="color: #7925ac;"&gt;length&lt;/span&gt; - &lt;span style="color: #2700dc;"&gt;2&lt;/span&gt;]; &lt;span style="color: #008b00;"&gt;// chop off trailing: ";&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                    &lt;/span&gt;[outputStrings &lt;span style="color: #430083;"&gt;appendFormat&lt;/span&gt;:&lt;span style="color: #e50000;"&gt;@"\"%@\" = \"%@\";\n\n"&lt;/span&gt;, stringNeedingLocalization, stringNeedingLocalization];&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                    &lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;continue&lt;/span&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;            &lt;/span&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class="p2"&gt;&lt;span class="Apple-converted-space"&gt;            &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #e50000;"&gt;&lt;span style="color: #000000;"&gt;&lt;span class="Apple-converted-space"&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span style="color: #430083;"&gt;NSLog&lt;/span&gt;&lt;span style="color: #000000;"&gt;(&lt;/span&gt;@"Warning: skipped garbage input line %d, contents: \"%@\""&lt;span style="color: #000000;"&gt;, lineCount, line);&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class="p2"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #430083;"&gt;&lt;span style="color: #000000;"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;if&lt;/span&gt;&lt;span style="color: #000000;"&gt; (outputStrings.&lt;/span&gt;&lt;span style="color: #7925ac;"&gt;length&lt;/span&gt;&lt;span style="color: #000000;"&gt; &amp;amp;&amp;amp; ![outputStrings &lt;/span&gt;writeToFile&lt;span style="color: #000000;"&gt;:[&lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSString&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;stringWithUTF8String&lt;span style="color: #000000;"&gt;:argv[&lt;/span&gt;&lt;span style="color: #2700dc;"&gt;2&lt;/span&gt;&lt;span style="color: #000000;"&gt;]] &lt;/span&gt;atomically&lt;span style="color: #000000;"&gt;:&lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;NO&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;encoding&lt;span style="color: #000000;"&gt;:&lt;/span&gt;NSUTF8StringEncoding&lt;span style="color: #000000;"&gt; &lt;/span&gt;error&lt;span style="color: #000000;"&gt;:&amp;amp;error]) {&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;            &lt;/span&gt;&lt;span style="color: #430083;"&gt;fprintf&lt;/span&gt;(&lt;span style="color: #814726;"&gt;stderr&lt;/span&gt;, &lt;span style="color: #e50000;"&gt;"Error writing %s: %s\n"&lt;/span&gt;, argv[&lt;span style="color: #2700dc;"&gt;2&lt;/span&gt;], error.localizedDescription.UTF8String);&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;            &lt;/span&gt;&lt;span style="color: #430083;"&gt;exit&lt;/span&gt; (-&lt;span style="color: #2700dc;"&gt;1&lt;/span&gt;);&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;    &lt;/span&gt;} [autoreleasePool &lt;span style="color: #430083;"&gt;release&lt;/span&gt;];&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;}&lt;/span&gt;&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;br /&gt;&lt;br /&gt;Finally, add this class to your main project. Whenever you load a XIB file now, this code will be invoked, and it’ll run through all the displayed strings in your XIB and see if there is a localization in the correspondingly-named strings file. (Eg, if you load “MainMenu.xib”, it’ll automatically be localized with strings from “MainMenu.strings”.)&lt;br /&gt;&lt;br /&gt;&lt;table class="codeTable"&gt;&lt;tr&gt;&lt;td class="codeHeader"&gt; DMLocalizedNibBundle.m: Run-time Localization of XIBs&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td  class="codeCell"&gt;&lt;br /&gt;&lt;span style="color: #008b00;"&gt;//&lt;span class="Apple-converted-space"&gt;  &lt;/span&gt;DMLocalizedNibBundle.m&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #008b00;"&gt;//&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #008b00;"&gt;//&lt;span class="Apple-converted-space"&gt;  &lt;/span&gt;Created by William Jon Shipley on 2/13/05.&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #008b00;"&gt;//&lt;span class="Apple-converted-space"&gt;  &lt;/span&gt;Copyright © 2005-2009 Golden % Braeburn, LLC. All rights reserved except as below:&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #008b00;"&gt;//&lt;span class="Apple-converted-space"&gt;  &lt;/span&gt;This code is provided as-is, with no warranties or anything. You may use it in your projects as you wish, but you must leave this comment block (credits and copyright) intact. That's the only restriction -- Golden % Braeburn otherwise grants you a fully-paid, worldwide, transferrable license to use this code as you see fit, including but not limited to making derivative works.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: #e50000;"&gt;&lt;span style="color: #814726;"&gt;#import &lt;/span&gt;&amp;lt;Cocoa/Cocoa.h&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #e50000;"&gt;&lt;span style="color: #814726;"&gt;#import &lt;/span&gt;&amp;lt;objc/runtime.h&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span style="color: #cd00a3;"&gt;@interface&lt;/span&gt; NSBundle (DMLocalizedNibBundle)&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;+ (&lt;span style="color: #cd00a3;"&gt;BOOL&lt;/span&gt;)deliciousLocalizingLoadNibFile:(&lt;span style="color: #7925ac;"&gt;NSString&lt;/span&gt; *)fileName externalNameTable:(&lt;span style="color: #7925ac;"&gt;NSDictionary&lt;/span&gt; *)context withZone:(&lt;span style="color: #7925ac;"&gt;NSZone&lt;/span&gt; *)zone;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #cd00a3;"&gt;@end&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span style="color: #cd00a3;"&gt;@interface&lt;/span&gt; NSBundle ()&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;+ (&lt;span style="color: #cd00a3;"&gt;void&lt;/span&gt;)_localizeStringsInObject:(&lt;span style="color: #cd00a3;"&gt;id&lt;/span&gt;)object table:(&lt;span style="color: #7925ac;"&gt;NSString&lt;/span&gt; *)table;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;+ (&lt;span style="color: #7925ac;"&gt;NSString&lt;/span&gt; *)_localizedStringForString:(&lt;span style="color: #7925ac;"&gt;NSString&lt;/span&gt; *)string table:(&lt;span style="color: #7925ac;"&gt;NSString&lt;/span&gt; *)table;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #008b00;"&gt;// localize particular attributes in objects&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;+ (&lt;span style="color: #cd00a3;"&gt;void&lt;/span&gt;)_localizeTitleOfObject:(&lt;span style="color: #cd00a3;"&gt;id&lt;/span&gt;)object table:(&lt;span style="color: #7925ac;"&gt;NSString&lt;/span&gt; *)table;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;+ (&lt;span style="color: #cd00a3;"&gt;void&lt;/span&gt;)_localizeAlternateTitleOfObject:(&lt;span style="color: #cd00a3;"&gt;id&lt;/span&gt;)object table:(&lt;span style="color: #7925ac;"&gt;NSString&lt;/span&gt; *)table;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;+ (&lt;span style="color: #cd00a3;"&gt;void&lt;/span&gt;)_localizeStringValueOfObject:(&lt;span style="color: #cd00a3;"&gt;id&lt;/span&gt;)object table:(&lt;span style="color: #7925ac;"&gt;NSString&lt;/span&gt; *)table;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;+ (&lt;span style="color: #cd00a3;"&gt;void&lt;/span&gt;)_localizePlaceholderStringOfObject:(&lt;span style="color: #cd00a3;"&gt;id&lt;/span&gt;)object table:(&lt;span style="color: #7925ac;"&gt;NSString&lt;/span&gt; *)table;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;+ (&lt;span style="color: #cd00a3;"&gt;void&lt;/span&gt;)_localizeToolTipOfObject:(&lt;span style="color: #cd00a3;"&gt;id&lt;/span&gt;)object table:(&lt;span style="color: #7925ac;"&gt;NSString&lt;/span&gt; *)table;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #cd00a3;"&gt;@end&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span style="color: #cd00a3;"&gt;@implementation&lt;/span&gt; NSBundle (DMLocalizedNibBundle)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: #814726;"&gt;#pragma mark NSObject&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="p4"&gt;+ (&lt;span style="color: #cd00a3;"&gt;void&lt;/span&gt;)load;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #7925ac;"&gt;&lt;span style="color: #000000;"&gt;&lt;span class="Apple-converted-space"&gt;    &lt;/span&gt;&lt;/span&gt;NSAutoreleasePool&lt;span style="color: #000000;"&gt; *autoreleasePool = [[&lt;/span&gt;NSAutoreleasePool&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #430083;"&gt;alloc&lt;/span&gt;&lt;span style="color: #000000;"&gt;] &lt;/span&gt;&lt;span style="color: #205a5e;"&gt;init&lt;/span&gt;&lt;span style="color: #000000;"&gt;];&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;    &lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;if&lt;/span&gt; (&lt;span style="color: #cd00a3;"&gt;self&lt;/span&gt; == [&lt;span style="color: #7925ac;"&gt;NSBundle&lt;/span&gt; &lt;span style="color: #430083;"&gt;class&lt;/span&gt;]) {&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #430083;"&gt;&lt;span style="color: #000000;"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;/span&gt;method_exchangeImplementations&lt;span style="color: #000000;"&gt;(&lt;/span&gt;class_getClassMethod&lt;span style="color: #000000;"&gt;(&lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;self&lt;/span&gt;&lt;span style="color: #000000;"&gt;, &lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;@selector&lt;/span&gt;&lt;span style="color: #000000;"&gt;(&lt;/span&gt;loadNibFile&lt;span style="color: #000000;"&gt;:&lt;/span&gt;externalNameTable&lt;span style="color: #000000;"&gt;:&lt;/span&gt;withZone&lt;span style="color: #000000;"&gt;:)), &lt;/span&gt;class_getClassMethod&lt;span style="color: #000000;"&gt;(&lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;self&lt;/span&gt;&lt;span style="color: #000000;"&gt;, &lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;@selector&lt;/span&gt;&lt;span style="color: #000000;"&gt;(&lt;/span&gt;&lt;span style="color: #205a5e;"&gt;deliciousLocalizingLoadNibFile&lt;/span&gt;&lt;span style="color: #000000;"&gt;:&lt;/span&gt;&lt;span style="color: #205a5e;"&gt;externalNameTable&lt;/span&gt;&lt;span style="color: #000000;"&gt;:&lt;/span&gt;&lt;span style="color: #205a5e;"&gt;withZone&lt;/span&gt;&lt;span style="color: #000000;"&gt;:)));&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;    &lt;/span&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;    &lt;/span&gt;[autoreleasePool &lt;span style="color: #430083;"&gt;release&lt;/span&gt;];&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: #814726;"&gt;#pragma mark API&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="p4"&gt;+ (&lt;span style="color: #cd00a3;"&gt;BOOL&lt;/span&gt;)deliciousLocalizingLoadNibFile:(&lt;span style="color: #7925ac;"&gt;NSString&lt;/span&gt; *)fileName externalNameTable:(&lt;span style="color: #7925ac;"&gt;NSDictionary&lt;/span&gt; *)context withZone:(&lt;span style="color: #7925ac;"&gt;NSZone&lt;/span&gt; *)zone;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;    &lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSString&lt;/span&gt; *localizedStringsTableName = [[fileName &lt;span style="color: #430083;"&gt;lastPathComponent&lt;/span&gt;] &lt;span style="color: #430083;"&gt;stringByDeletingPathExtension&lt;/span&gt;];&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;    &lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSString&lt;/span&gt; *localizedStringsTablePath = [[&lt;span style="color: #7925ac;"&gt;NSBundle&lt;/span&gt; &lt;span style="color: #430083;"&gt;mainBundle&lt;/span&gt;] &lt;span style="color: #430083;"&gt;pathForResource&lt;/span&gt;:localizedStringsTableName &lt;span style="color: #430083;"&gt;ofType&lt;/span&gt;:&lt;span style="color: #e50000;"&gt;@"strings"&lt;/span&gt;];&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;    &lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;if&lt;/span&gt; (localizedStringsTablePath &amp;amp;&amp;amp; ![[[localizedStringsTablePath &lt;span style="color: #430083;"&gt;stringByDeletingLastPathComponent&lt;/span&gt;] &lt;span style="color: #430083;"&gt;lastPathComponent&lt;/span&gt;] &lt;span style="color: #430083;"&gt;isEqualToString&lt;/span&gt;:&lt;span style="color: #e50000;"&gt;@"English.lproj"&lt;/span&gt;]) {&lt;/span&gt;&lt;br /&gt;&lt;span class="p2"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #430083;"&gt;&lt;span style="color: #000000;"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSNib&lt;/span&gt;&lt;span style="color: #000000;"&gt; *nib = [[&lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSNib&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;alloc&lt;span style="color: #000000;"&gt;] &lt;/span&gt;initWithContentsOfURL&lt;span style="color: #000000;"&gt;:[&lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSURL&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;fileURLWithPath&lt;span style="color: #000000;"&gt;:fileName]];&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSMutableArray&lt;/span&gt; *topLevelObjectsArray = [context &lt;span style="color: #430083;"&gt;objectForKey&lt;/span&gt;:&lt;span style="color: #7925ac;"&gt;NSNibTopLevelObjects&lt;/span&gt;];&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;if&lt;/span&gt; (!topLevelObjectsArray) {&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;            &lt;/span&gt;topLevelObjectsArray = [&lt;span style="color: #7925ac;"&gt;NSMutableArray&lt;/span&gt; &lt;span style="color: #430083;"&gt;array&lt;/span&gt;];&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;            &lt;/span&gt;context = [&lt;span style="color: #7925ac;"&gt;NSMutableDictionary&lt;/span&gt; &lt;span style="color: #430083;"&gt;dictionaryWithDictionary&lt;/span&gt;:context];&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;            &lt;/span&gt;[(&lt;span style="color: #7925ac;"&gt;NSMutableDictionary&lt;/span&gt; *)context &lt;span style="color: #430083;"&gt;setObject&lt;/span&gt;:topLevelObjectsArray &lt;span style="color: #430083;"&gt;forKey&lt;/span&gt;:&lt;span style="color: #7925ac;"&gt;NSNibTopLevelObjects&lt;/span&gt;];&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #430083;"&gt;&lt;span style="color: #000000;"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;BOOL&lt;/span&gt;&lt;span style="color: #000000;"&gt; success = [nib &lt;/span&gt;instantiateNibWithExternalNameTable&lt;span style="color: #000000;"&gt;:context];&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;[&lt;span style="color: #cd00a3;"&gt;self&lt;/span&gt; &lt;span style="color: #205a5e;"&gt;_localizeStringsInObject&lt;/span&gt;:topLevelObjectsArray &lt;span style="color: #205a5e;"&gt;table&lt;/span&gt;:localizedStringsTableName];&lt;/span&gt;&lt;br /&gt;&lt;span class="p2"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;[nib &lt;span style="color: #430083;"&gt;release&lt;/span&gt;];&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;return&lt;/span&gt; success;&lt;/span&gt;&lt;br /&gt;&lt;span class="p2"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;    &lt;/span&gt;} &lt;span style="color: #cd00a3;"&gt;else&lt;/span&gt; {&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #205a5e;"&gt;&lt;span style="color: #000000;"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;return&lt;/span&gt;&lt;span style="color: #000000;"&gt; [&lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;self&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;deliciousLocalizingLoadNibFile&lt;span style="color: #000000;"&gt;:fileName &lt;/span&gt;externalNameTable&lt;span style="color: #000000;"&gt;:context &lt;/span&gt;withZone&lt;span style="color: #000000;"&gt;:zone];&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;    &lt;/span&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: #814726;"&gt;#pragma mark Private API&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="p4"&gt;+ (&lt;span style="color: #cd00a3;"&gt;void&lt;/span&gt;)_localizeStringsInObject:(&lt;span style="color: #cd00a3;"&gt;id&lt;/span&gt;)object table:(&lt;span style="color: #7925ac;"&gt;NSString&lt;/span&gt; *)table;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;    &lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;if&lt;/span&gt; ([object &lt;span style="color: #430083;"&gt;isKindOfClass&lt;/span&gt;:[&lt;span style="color: #7925ac;"&gt;NSArray&lt;/span&gt; &lt;span style="color: #430083;"&gt;class&lt;/span&gt;]]) {&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSArray&lt;/span&gt; *array = object;&lt;/span&gt;&lt;br /&gt;&lt;span class="p2"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;for&lt;/span&gt; (&lt;span style="color: #cd00a3;"&gt;id&lt;/span&gt; nibItem &lt;span style="color: #cd00a3;"&gt;in&lt;/span&gt; array)&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;            &lt;/span&gt;[&lt;span style="color: #cd00a3;"&gt;self&lt;/span&gt; &lt;span style="color: #205a5e;"&gt;_localizeStringsInObject&lt;/span&gt;:nibItem &lt;span style="color: #205a5e;"&gt;table&lt;/span&gt;:table];&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;    &lt;/span&gt;} &lt;span style="color: #cd00a3;"&gt;else&lt;/span&gt; &lt;span style="color: #cd00a3;"&gt;if&lt;/span&gt; ([object &lt;span style="color: #430083;"&gt;isKindOfClass&lt;/span&gt;:[&lt;span style="color: #7925ac;"&gt;NSCell&lt;/span&gt; &lt;span style="color: #430083;"&gt;class&lt;/span&gt;]]) {&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSCell&lt;/span&gt; *cell = object;&lt;/span&gt;&lt;br /&gt;&lt;span class="p2"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;if&lt;/span&gt; ([cell &lt;span style="color: #430083;"&gt;isKindOfClass&lt;/span&gt;:[&lt;span style="color: #7925ac;"&gt;NSActionCell&lt;/span&gt; &lt;span style="color: #430083;"&gt;class&lt;/span&gt;]]) {&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;            &lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSActionCell&lt;/span&gt; *actionCell = (&lt;span style="color: #7925ac;"&gt;NSActionCell&lt;/span&gt; *)cell;&lt;/span&gt;&lt;br /&gt;&lt;span class="p2"&gt;&lt;span class="Apple-converted-space"&gt;            &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;            &lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;if&lt;/span&gt; ([actionCell &lt;span style="color: #430083;"&gt;isKindOfClass&lt;/span&gt;:[&lt;span style="color: #7925ac;"&gt;NSButtonCell&lt;/span&gt; &lt;span style="color: #430083;"&gt;class&lt;/span&gt;]]) {&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSButtonCell&lt;/span&gt; *buttonCell = (&lt;span style="color: #7925ac;"&gt;NSButtonCell&lt;/span&gt; *)actionCell;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;if&lt;/span&gt; ([buttonCell &lt;span style="color: #430083;"&gt;imagePosition&lt;/span&gt;] != &lt;span style="color: #430083;"&gt;NSImageOnly&lt;/span&gt;) {&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                    &lt;/span&gt;[&lt;span style="color: #cd00a3;"&gt;self&lt;/span&gt; &lt;span style="color: #205a5e;"&gt;_localizeTitleOfObject&lt;/span&gt;:buttonCell &lt;span style="color: #205a5e;"&gt;table&lt;/span&gt;:table];&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                    &lt;/span&gt;[&lt;span style="color: #cd00a3;"&gt;self&lt;/span&gt; &lt;span style="color: #205a5e;"&gt;_localizeStringValueOfObject&lt;/span&gt;:buttonCell &lt;span style="color: #205a5e;"&gt;table&lt;/span&gt;:table];&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                    &lt;/span&gt;[&lt;span style="color: #cd00a3;"&gt;self&lt;/span&gt; &lt;span style="color: #205a5e;"&gt;_localizeAlternateTitleOfObject&lt;/span&gt;:buttonCell &lt;span style="color: #205a5e;"&gt;table&lt;/span&gt;:table];&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class="p2"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;            &lt;/span&gt;} &lt;span style="color: #cd00a3;"&gt;else&lt;/span&gt; &lt;span style="color: #cd00a3;"&gt;if&lt;/span&gt; ([actionCell &lt;span style="color: #430083;"&gt;isKindOfClass&lt;/span&gt;:[&lt;span style="color: #7925ac;"&gt;NSTextFieldCell&lt;/span&gt; &lt;span style="color: #430083;"&gt;class&lt;/span&gt;]]) {&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSTextFieldCell&lt;/span&gt; *textFieldCell = (&lt;span style="color: #7925ac;"&gt;NSTextFieldCell&lt;/span&gt; *)actionCell;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #008b00;"&gt;&lt;span style="color: #000000;"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;&lt;/span&gt;// Following line is redundant with other code, localizes twice.&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #008b00;"&gt;&lt;span style="color: #000000;"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;&lt;/span&gt;// [self _localizeTitleOfObject:textFieldCell table:table];&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;[&lt;span style="color: #cd00a3;"&gt;self&lt;/span&gt; &lt;span style="color: #205a5e;"&gt;_localizeStringValueOfObject&lt;/span&gt;:textFieldCell &lt;span style="color: #205a5e;"&gt;table&lt;/span&gt;:table];&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;[&lt;span style="color: #cd00a3;"&gt;self&lt;/span&gt; &lt;span style="color: #205a5e;"&gt;_localizePlaceholderStringOfObject&lt;/span&gt;:textFieldCell &lt;span style="color: #205a5e;"&gt;table&lt;/span&gt;:table];&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;            &lt;/span&gt;} &lt;span style="color: #cd00a3;"&gt;else&lt;/span&gt; &lt;span style="color: #cd00a3;"&gt;if&lt;/span&gt; ([actionCell &lt;span style="color: #205a5e;"&gt;type&lt;/span&gt;] == &lt;span style="color: #430083;"&gt;NSTextCellType&lt;/span&gt;) {&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;[&lt;span style="color: #cd00a3;"&gt;self&lt;/span&gt; &lt;span style="color: #205a5e;"&gt;_localizeTitleOfObject&lt;/span&gt;:actionCell &lt;span style="color: #205a5e;"&gt;table&lt;/span&gt;:table];&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;[&lt;span style="color: #cd00a3;"&gt;self&lt;/span&gt; &lt;span style="color: #205a5e;"&gt;_localizeStringValueOfObject&lt;/span&gt;:actionCell &lt;span style="color: #205a5e;"&gt;table&lt;/span&gt;:table];&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;            &lt;/span&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class="p2"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;    &lt;/span&gt;} &lt;span style="color: #cd00a3;"&gt;else&lt;/span&gt; &lt;span style="color: #cd00a3;"&gt;if&lt;/span&gt; ([object &lt;span style="color: #430083;"&gt;isKindOfClass&lt;/span&gt;:[&lt;span style="color: #7925ac;"&gt;NSMenu&lt;/span&gt; &lt;span style="color: #430083;"&gt;class&lt;/span&gt;]]) {&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSMenu&lt;/span&gt; *menu = object;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #205a5e;"&gt;&lt;span style="color: #000000;"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;[&lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;self&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;_localizeTitleOfObject&lt;span style="color: #000000;"&gt;:menu &lt;/span&gt;table&lt;span style="color: #000000;"&gt;:table];&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p2"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #205a5e;"&gt;&lt;span style="color: #000000;"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;[&lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;self&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;_localizeStringsInObject&lt;span style="color: #000000;"&gt;:[menu &lt;/span&gt;&lt;span style="color: #430083;"&gt;itemArray&lt;/span&gt;&lt;span style="color: #000000;"&gt;] &lt;/span&gt;table&lt;span style="color: #000000;"&gt;:table];&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p2"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;    &lt;/span&gt;} &lt;span style="color: #cd00a3;"&gt;else&lt;/span&gt; &lt;span style="color: #cd00a3;"&gt;if&lt;/span&gt; ([object &lt;span style="color: #430083;"&gt;isKindOfClass&lt;/span&gt;:[&lt;span style="color: #7925ac;"&gt;NSMenuItem&lt;/span&gt; &lt;span style="color: #430083;"&gt;class&lt;/span&gt;]]) {&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSMenuItem&lt;/span&gt; *menuItem = object;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;[&lt;span style="color: #cd00a3;"&gt;self&lt;/span&gt; &lt;span style="color: #205a5e;"&gt;_localizeTitleOfObject&lt;/span&gt;:menuItem &lt;span style="color: #205a5e;"&gt;table&lt;/span&gt;:table];&lt;/span&gt;&lt;br /&gt;&lt;span class="p2"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;[&lt;span style="color: #cd00a3;"&gt;self&lt;/span&gt; &lt;span style="color: #205a5e;"&gt;_localizeStringsInObject&lt;/span&gt;:[menuItem &lt;span style="color: #430083;"&gt;submenu&lt;/span&gt;] &lt;span style="color: #205a5e;"&gt;table&lt;/span&gt;:table];&lt;/span&gt;&lt;br /&gt;&lt;span class="p2"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;    &lt;/span&gt;} &lt;span style="color: #cd00a3;"&gt;else&lt;/span&gt; &lt;span style="color: #cd00a3;"&gt;if&lt;/span&gt; ([object &lt;span style="color: #430083;"&gt;isKindOfClass&lt;/span&gt;:[&lt;span style="color: #7925ac;"&gt;NSView&lt;/span&gt; &lt;span style="color: #430083;"&gt;class&lt;/span&gt;]]) {&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSView&lt;/span&gt; *view = object;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #205a5e;"&gt;&lt;span style="color: #000000;"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;[&lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;self&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;_localizeToolTipOfObject&lt;span style="color: #000000;"&gt;:view &lt;/span&gt;table&lt;span style="color: #000000;"&gt;:table];&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;if&lt;/span&gt; ([view &lt;span style="color: #430083;"&gt;isKindOfClass&lt;/span&gt;:[&lt;span style="color: #7925ac;"&gt;NSBox&lt;/span&gt; &lt;span style="color: #430083;"&gt;class&lt;/span&gt;]]) {&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;            &lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSBox&lt;/span&gt; *box = (&lt;span style="color: #7925ac;"&gt;NSBox&lt;/span&gt; *)view;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;            &lt;/span&gt;[&lt;span style="color: #cd00a3;"&gt;self&lt;/span&gt; &lt;span style="color: #205a5e;"&gt;_localizeTitleOfObject&lt;/span&gt;:box &lt;span style="color: #205a5e;"&gt;table&lt;/span&gt;:table];&lt;/span&gt;&lt;br /&gt;&lt;span class="p2"&gt;&lt;span class="Apple-converted-space"&gt;            &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;} &lt;span style="color: #cd00a3;"&gt;else&lt;/span&gt; &lt;span style="color: #cd00a3;"&gt;if&lt;/span&gt; ([view &lt;span style="color: #430083;"&gt;isKindOfClass&lt;/span&gt;:[&lt;span style="color: #7925ac;"&gt;NSControl&lt;/span&gt; &lt;span style="color: #430083;"&gt;class&lt;/span&gt;]]) {&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;            &lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSControl&lt;/span&gt; *control = (&lt;span style="color: #7925ac;"&gt;NSControl&lt;/span&gt; *)view;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;            &lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;if&lt;/span&gt; ([view &lt;span style="color: #430083;"&gt;isKindOfClass&lt;/span&gt;:[&lt;span style="color: #7925ac;"&gt;NSButton&lt;/span&gt; &lt;span style="color: #430083;"&gt;class&lt;/span&gt;]]) {&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSButton&lt;/span&gt; *button = (&lt;span style="color: #7925ac;"&gt;NSButton&lt;/span&gt; *)control;&lt;/span&gt;&lt;br /&gt;&lt;span class="p2"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;if&lt;/span&gt; ([button &lt;span style="color: #430083;"&gt;isKindOfClass&lt;/span&gt;:[&lt;span style="color: #7925ac;"&gt;NSPopUpButton&lt;/span&gt; &lt;span style="color: #430083;"&gt;class&lt;/span&gt;]]) {&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                    &lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSPopUpButton&lt;/span&gt; *popUpButton = (&lt;span style="color: #7925ac;"&gt;NSPopUpButton&lt;/span&gt; *)button;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                    &lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSMenu&lt;/span&gt; *menu = [popUpButton &lt;span style="color: #430083;"&gt;menu&lt;/span&gt;];&lt;/span&gt;&lt;br /&gt;&lt;span class="p2"&gt;&lt;span class="Apple-converted-space"&gt;                    &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                    &lt;/span&gt;[&lt;span style="color: #cd00a3;"&gt;self&lt;/span&gt; &lt;span style="color: #205a5e;"&gt;_localizeStringsInObject&lt;/span&gt;:[menu &lt;span style="color: #430083;"&gt;itemArray&lt;/span&gt;] &lt;span style="color: #205a5e;"&gt;table&lt;/span&gt;:table];&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;} &lt;span style="color: #cd00a3;"&gt;else&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                    &lt;/span&gt;[&lt;span style="color: #cd00a3;"&gt;self&lt;/span&gt; &lt;span style="color: #205a5e;"&gt;_localizeStringsInObject&lt;/span&gt;:[button &lt;span style="color: #430083;"&gt;cell&lt;/span&gt;] &lt;span style="color: #205a5e;"&gt;table&lt;/span&gt;:table];&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="p2"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;            &lt;/span&gt;} &lt;span style="color: #cd00a3;"&gt;else&lt;/span&gt; &lt;span style="color: #cd00a3;"&gt;if&lt;/span&gt; ([view &lt;span style="color: #430083;"&gt;isKindOfClass&lt;/span&gt;:[&lt;span style="color: #7925ac;"&gt;NSMatrix&lt;/span&gt; &lt;span style="color: #430083;"&gt;class&lt;/span&gt;]]) {&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSMatrix&lt;/span&gt; *matrix = (&lt;span style="color: #7925ac;"&gt;NSMatrix&lt;/span&gt; *)control;&lt;/span&gt;&lt;br /&gt;&lt;span class="p2"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSArray&lt;/span&gt; *cells = [matrix &lt;span style="color: #430083;"&gt;cells&lt;/span&gt;];&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;[&lt;span style="color: #cd00a3;"&gt;self&lt;/span&gt; &lt;span style="color: #205a5e;"&gt;_localizeStringsInObject&lt;/span&gt;:cells &lt;span style="color: #205a5e;"&gt;table&lt;/span&gt;:table];&lt;/span&gt;&lt;br /&gt;&lt;span class="p2"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;for&lt;/span&gt; (&lt;span style="color: #7925ac;"&gt;NSCell&lt;/span&gt; *cell &lt;span style="color: #cd00a3;"&gt;in&lt;/span&gt; cells) {&lt;/span&gt;&lt;br /&gt;&lt;span class="p2"&gt;&lt;span class="Apple-converted-space"&gt;                    &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                    &lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSString&lt;/span&gt; *localizedCellToolTip = [&lt;span style="color: #cd00a3;"&gt;self&lt;/span&gt; &lt;span style="color: #205a5e;"&gt;_localizedStringForString&lt;/span&gt;:[matrix &lt;span style="color: #430083;"&gt;toolTipForCell&lt;/span&gt;:cell] &lt;span style="color: #205a5e;"&gt;table&lt;/span&gt;:table];&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                    &lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;if&lt;/span&gt; (localizedCellToolTip)&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                        &lt;/span&gt;[matrix &lt;span style="color: #430083;"&gt;setToolTip&lt;/span&gt;:localizedCellToolTip &lt;span style="color: #430083;"&gt;forCell&lt;/span&gt;:cell];&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class="p2"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;            &lt;/span&gt;} &lt;span style="color: #cd00a3;"&gt;else&lt;/span&gt; &lt;span style="color: #cd00a3;"&gt;if&lt;/span&gt; ([view &lt;span style="color: #430083;"&gt;isKindOfClass&lt;/span&gt;:[&lt;span style="color: #7925ac;"&gt;NSSegmentedControl&lt;/span&gt; &lt;span style="color: #430083;"&gt;class&lt;/span&gt;]]) {&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSSegmentedControl&lt;/span&gt; *segmentedControl = (&lt;span style="color: #7925ac;"&gt;NSSegmentedControl&lt;/span&gt; *)control;&lt;/span&gt;&lt;br /&gt;&lt;span class="p2"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSUInteger&lt;/span&gt; segmentIndex, segmentCount = [segmentedControl &lt;span style="color: #430083;"&gt;segmentCount&lt;/span&gt;];&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;for&lt;/span&gt; (segmentIndex = &lt;span style="color: #2700dc;"&gt;0&lt;/span&gt;; segmentIndex &amp;lt; segmentCount; segmentIndex++) {&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                    &lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSString&lt;/span&gt; *localizedSegmentLabel = [&lt;span style="color: #cd00a3;"&gt;self&lt;/span&gt; &lt;span style="color: #205a5e;"&gt;_localizedStringForString&lt;/span&gt;:[segmentedControl &lt;span style="color: #430083;"&gt;labelForSegment&lt;/span&gt;:segmentIndex] &lt;span style="color: #205a5e;"&gt;table&lt;/span&gt;:table];&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                    &lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;if&lt;/span&gt; (localizedSegmentLabel)&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                        &lt;/span&gt;[segmentedControl &lt;span style="color: #430083;"&gt;setLabel&lt;/span&gt;:localizedSegmentLabel &lt;span style="color: #430083;"&gt;forSegment&lt;/span&gt;:segmentIndex];&lt;/span&gt;&lt;br /&gt;&lt;span class="p2"&gt;&lt;span class="Apple-converted-space"&gt;                    &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                    &lt;/span&gt;[&lt;span style="color: #cd00a3;"&gt;self&lt;/span&gt; &lt;span style="color: #205a5e;"&gt;_localizeStringsInObject&lt;/span&gt;:[segmentedControl &lt;span style="color: #430083;"&gt;menuForSegment&lt;/span&gt;:segmentIndex] &lt;span style="color: #205a5e;"&gt;table&lt;/span&gt;:table];&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class="p2"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;            &lt;/span&gt;} &lt;span style="color: #cd00a3;"&gt;else&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;                &lt;/span&gt;[&lt;span style="color: #cd00a3;"&gt;self&lt;/span&gt; &lt;span style="color: #205a5e;"&gt;_localizeStringsInObject&lt;/span&gt;:[control &lt;span style="color: #430083;"&gt;cell&lt;/span&gt;] &lt;span style="color: #205a5e;"&gt;table&lt;/span&gt;:table];&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class="p2"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #205a5e;"&gt;&lt;span style="color: #000000;"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;[&lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;self&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;_localizeStringsInObject&lt;span style="color: #000000;"&gt;:[view &lt;/span&gt;&lt;span style="color: #430083;"&gt;subviews&lt;/span&gt;&lt;span style="color: #000000;"&gt;] &lt;/span&gt;table&lt;span style="color: #000000;"&gt;:table];&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p2"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;    &lt;/span&gt;} &lt;span style="color: #cd00a3;"&gt;else&lt;/span&gt; &lt;span style="color: #cd00a3;"&gt;if&lt;/span&gt; ([object &lt;span style="color: #430083;"&gt;isKindOfClass&lt;/span&gt;:[&lt;span style="color: #7925ac;"&gt;NSWindow&lt;/span&gt; &lt;span style="color: #430083;"&gt;class&lt;/span&gt;]]) {&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSWindow&lt;/span&gt; *window = object;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #205a5e;"&gt;&lt;span style="color: #000000;"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;[&lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;self&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;_localizeTitleOfObject&lt;span style="color: #000000;"&gt;:window &lt;/span&gt;table&lt;span style="color: #000000;"&gt;:table];&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p2"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;[&lt;span style="color: #cd00a3;"&gt;self&lt;/span&gt; &lt;span style="color: #205a5e;"&gt;_localizeStringsInObject&lt;/span&gt;:[window &lt;span style="color: #430083;"&gt;contentView&lt;/span&gt;] &lt;span style="color: #205a5e;"&gt;table&lt;/span&gt;:table];&lt;/span&gt;&lt;br /&gt;&lt;span class="p2"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;    &lt;/span&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="p4"&gt;+ (&lt;span style="color: #7925ac;"&gt;NSString&lt;/span&gt; *)_localizedStringForString:(&lt;span style="color: #7925ac;"&gt;NSString&lt;/span&gt; *)string table:(&lt;span style="color: #7925ac;"&gt;NSString&lt;/span&gt; *)table;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;    &lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;if&lt;/span&gt; (![string &lt;span style="color: #430083;"&gt;length&lt;/span&gt;])&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;return&lt;/span&gt; &lt;span style="color: #cd00a3;"&gt;nil&lt;/span&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class="p2"&gt;&lt;span class="Apple-converted-space"&gt;    &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #e50000;"&gt;&lt;span style="color: #000000;"&gt;&lt;span class="Apple-converted-space"&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;static&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSString&lt;/span&gt;&lt;span style="color: #000000;"&gt; *defaultValue = &lt;/span&gt;@"I AM THE DEFAULT VALUE"&lt;span style="color: #000000;"&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;    &lt;/span&gt;&lt;span style="color: #7925ac;"&gt;NSString&lt;/span&gt; *localizedString = [[&lt;span style="color: #7925ac;"&gt;NSBundle&lt;/span&gt; &lt;span style="color: #430083;"&gt;mainBundle&lt;/span&gt;] &lt;span style="color: #430083;"&gt;localizedStringForKey&lt;/span&gt;:string &lt;span style="color: #430083;"&gt;value&lt;/span&gt;:defaultValue &lt;span style="color: #430083;"&gt;table&lt;/span&gt;:table];&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;    &lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;if&lt;/span&gt; (localizedString != defaultValue) {&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;return&lt;/span&gt; localizedString;&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;    &lt;/span&gt;} &lt;span style="color: #cd00a3;"&gt;else&lt;/span&gt; {&lt;span class="Apple-converted-space"&gt; &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #814726;"&gt;#ifdef BETA_BUILD&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #e50000;"&gt;&lt;span style="color: #000000;"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span style="color: #430083;"&gt;NSLog&lt;/span&gt;&lt;span style="color: #000000;"&gt;(&lt;/span&gt;@"&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;not going to localize string %@"&lt;span style="color: #000000;"&gt;, string);&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #008b00;"&gt;&lt;span style="color: #000000;"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;return&lt;/span&gt;&lt;span style="color: #000000;"&gt; string; &lt;/span&gt;// [string uppercaseString]&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #814726;"&gt;#else&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;&lt;span style="color: #cd00a3;"&gt;return&lt;/span&gt; string;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #814726;"&gt;#endif&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;&lt;span class="Apple-converted-space"&gt;    &lt;/span&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: #814726;"&gt;#define DM_DEFINE_LOCALIZE_BLAH_OF_OBJECT(blahName, capitalizedBlahName) \&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #814726;"&gt;+ (void)_localize ##capitalizedBlahName ##OfObject:(id)object table:(NSString *)table; \&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #814726;"&gt;{ \&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #814726;"&gt;&lt;span class="Apple-converted-space"&gt;    &lt;/span&gt;NSString *localizedBlah = [self _localizedStringForString:[object blahName] table:table]; \&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #814726;"&gt;&lt;span class="Apple-converted-space"&gt;    &lt;/span&gt;if (localizedBlah) \&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #814726;"&gt;&lt;span class="Apple-converted-space"&gt;        &lt;/span&gt;[object set ##capitalizedBlahName:localizedBlah]; \&lt;/span&gt;&lt;br /&gt;&lt;span style="color: #814726;"&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="p4"&gt;DM_DEFINE_LOCALIZE_BLAH_OF_OBJECT(title, Title)&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;DM_DEFINE_LOCALIZE_BLAH_OF_OBJECT(alternateTitle, AlternateTitle)&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;DM_DEFINE_LOCALIZE_BLAH_OF_OBJECT(stringValue, StringValue)&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;DM_DEFINE_LOCALIZE_BLAH_OF_OBJECT(placeholderString, PlaceholderString)&lt;/span&gt;&lt;br /&gt;&lt;span class="p4"&gt;DM_DEFINE_LOCALIZE_BLAH_OF_OBJECT(toolTip, ToolTip)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: #cd00a3;"&gt;@end&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;br /&gt;&lt;h3&gt;Unresolved Issues&lt;/h3&gt;&lt;strong&gt;Issue 1: Not all XIB properties are localized&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;I didn’t have any NSTableViews with standard column headers in Delicious Library, so there’s no code to handle NSTableViews at all in here. I also don’t even attempt to localize bindings strings, I believe. So, that’d be a good thing to do!&lt;br /&gt;&lt;br /&gt;I haven’t touched this code in, like, four years. Which says a lot about how well it’s worked for me, but, obviously, there’s room for improving it. So, please pimp *my* code, and send it back to me, and I’ll post your changes. (We’ll all be better off for it!)&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Issue 2: You can’t use the same phrase with two different meanings in a single XIB&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;Imagine if you have, say, a button labelled “Wind” for “what you do to a watch,” and a text field labelled “Wind” for “moving air.” You obviously can’t localize ‘em with this method. Hasn’t proven to be a big deal with me, but this is clearly what motivated Apple to completely hose string the file format for “ibtool.”&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Issue 3: iPhone limitations&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;I never got around to porting this to work on the iPhone, since &lt;em&gt;Amazon made me destroy the iPhone version of Delicious Library,&lt;/em&gt; but it’d be pretty easy to make this work with hierarchies of UIViews and UIControls instead of their NSCousins.&lt;br /&gt;&lt;br /&gt;I don’t think the swap-methods-at-load technique will work on the iPhone, either, so you’d have to manually call the translation method when you loaded a XIB, but I don’t think that’s a huge deal, since on the iPhone you typically are MUCH more conscious of when you load and unload XIBs.&lt;br /&gt;&lt;br /&gt;Also, obviously your customers couldn’t simply get the .strings from your app bundle or add their localizations themselves, so you lose those advantages. Still, this is the technique I’ll be using in my future iPhone and apps.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Issue 4: You can’t change the geometry of XIBs depending on the localization&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;The method requires that we, as programmers and interface designers, agree to make ALL our text fields and checkbuttons wide enough to allow for the wordiest of languages, since we’re localizing on-the-fly.&lt;br /&gt;&lt;br /&gt;Everyone raises this as an objection, but, seriously, the alternatives are MUCH worse — either you maintain ten sets of geometries yourself, OR you let your localizers be your interface designers. Both are horrible.&lt;br /&gt;&lt;br /&gt;And, seriously, you’d be surprised how little people notice when you have extra space. With checkboxes, just always stretch ‘em to the edge of the screen. With text fields — well, the user can’t tell HOW wide they are, can she? The only problem one is buttons, and I think those look better with extra space around them. (Update: my German localizer wrote me to tell me he disagreed with this — that it was a huge pain for him to find short enough German words. I apologized to him for not telling him he could request more width.)&lt;br /&gt;&lt;br /&gt;I’ve even thought up two ways around this issue: one if obvious, which is you could just store some geometry changes based on the container type &amp; string in a separate file, and merge those changes at run-time. But, that’d be hard to maintain, for reasons I’ve gone over a bunch by now.&lt;br /&gt;&lt;br /&gt;The other would be cooler: have your widgets re-lay themselves out at run time based on their resizing properties if their localized titles don’t fit. For example, if button “a” has a button “b” 20 pixels to the right of it, and both buttons have the stretchy spring to their right, then it’s a fair guess that if you grow/shrink button “a”, you need to do so from its right edge, and you need to move “a” by that many pixels, as well. The other combinations of stretchy springs can be assigned values, and all you have to do is use them consistently in your XIBs and you’ll be golden. (You do have to watch for accidentally localizing Apple’s XIBs.)&lt;br /&gt;&lt;br /&gt;But, seriously, I haven’t really done either of these yet, and no English speaker has ever said, “Hey, you have too much space around this button!”&lt;br /&gt;&lt;br /&gt;And, importantly, I’m able to integrate all thirteen localizations of Delicious Library 2 by myself, and still have time to program and run my company and drink lots of mojitos.&lt;br /&gt;&lt;h3&gt;Footnotes&lt;/h3&gt;&lt;super&gt;*&lt;/super&gt;No, it isn't.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/11049281-2240640073652448426?l=wilshipley.com%2Fblog' alt='' /&gt;&lt;/div&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/2240640073652448426'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/2240640073652448426'/><link rel='alternate' type='text/html' href='http://wilshipley.com/blog/2009/10/pimp-my-code-part-17-lost-in.html' title='Pimp My Code, Part 17: Lost in Translations.'/><author><name>Wil Shipley</name><uri>http://www.blogger.com/profile/04429428424208378747</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='07967666282086327688'/></author></entry><entry><id>tag:blogger.com,1999:blog-11049281.post-4032813207250805473</id><published>2009-08-21T03:49:00.001-07:00</published><updated>2009-08-24T21:38:37.575-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='interface design'/><category scheme='http://www.blogger.com/atom/ns#' term='code'/><title type='text'>Pimp My Code, Part 16: On Heuristics and Human Factors</title><content type='html'>First off, I should mention that I don't think I use the word "heuristic" correctly – although in computer science it's grown to replace the word "algorithm" as just a general term for a way to solve a problem, traditionally it has a more narrow definition:&lt;br /&gt;&lt;blockquote&gt;&lt;em&gt;&lt;strong&gt;Heuristic&lt;/strong&gt; (/hjʊˈrɪs.tɪk/) is an adjective for experience-based techniques that help in problem solving, learning and discovery.&lt;/em&gt;&lt;/blockquote&gt;(Yay, it's fun to copy and paste from Wikipedia!)&lt;br /&gt;&lt;br /&gt;When I use it, I usually am talking about an algorithm that won't &lt;em&gt;always&lt;/em&gt; give the correct solution, but does so often enough that the algorithm is useful. This differs from a classic algorithm, where we struggle mightily to make it &lt;em&gt;provably&lt;/em&gt; correct in &lt;em&gt;every&lt;/em&gt; instance.&lt;br /&gt;&lt;br /&gt;But classic computer programming has largely failed, because it failed to copy nature. Nothing in nature works 100% of the time, but it sure works well MOST of the time – and when it fails, well, you die and get replaced. A human being, for instance, is an absolutely amazing machine, and is provably NOT provably correct.&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;To talk about computer heuristics, we'll need to get concrete. Could I get a "for instance?" Heck, yes.&lt;br /&gt;&lt;br /&gt;For instance: NSDateFormatter has the following method:&lt;br /&gt;&lt;br /&gt;&lt;table class="codeTable"&gt;&lt;tr&gt;&lt;td class="codeHeader"&gt;NSDateFormatter  (NSDateFormatterCompatibility)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td  class="codeCell"&gt;- (id)initWithDateFormat:(NSString *)format allowNaturalLanguage:(BOOL)flag;&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;br /&gt;Ignoring for a second that 'flag' should be &lt;em&gt;really&lt;/em&gt; be renamed 'allowNaturalLanguage' (I mean, honestly, if your method body refers to a variable named 'flag' it's not at ALL obvious what you mean, is it? You'd have to look at the method definition every time you saw this 'flag' variable, and that's just poor coding.) uh I lost my train of thought.&lt;br /&gt;&lt;br /&gt;Oh! Yes. 'allowNaturalLanguage.' It's really cool! In most places you can enter a textual date in Cocoa (but &lt;em&gt;not&lt;/em&gt; those fiddly NSDatePicker widgets where each number is in its own field), you can enter dates like, "Next Tuesday at noon" or "yesterday at 5:23PM" and Cocoa will magically turn that into a valid NSDate.&lt;br /&gt;&lt;br /&gt;Possibly more importantly, you can type "Oct 16, 1969" or "10/16/69" or "10.16.1969" and it'll figure out what you meant my birthday in each case. (You &lt;em&gt;cannot&lt;/em&gt; just type "Wil Shipley's Birthday" but that would be a great extension of their existing heuristic, if you ask me.)&lt;br /&gt;&lt;br /&gt;This frees users up from having to figure out what magic combination of digits, dashes, slashes, words, and/or abbreviations comprise a valid date. Without this flag the programmer specifies exactly what format dates must take, like, "mm/dd/yyyy", and if the user doesn't type &lt;strong&gt;exactly&lt;/strong&gt; that information, she gets an ugly error panel. With the flag, the user doesn't have to learn what the computer wants: &lt;em&gt;she can continue to do things the way she has done them and the computer will understand her.&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;The latter is the touchstone of great design: we must strive to make our programs require as little learning as possible on the user's part. Each little thing they have to learn about our program is another obstacle to them using it fully, another tiny chunk of enjoyment stripped from their experience.&lt;br /&gt;&lt;br /&gt;Now, a few releases of OS X ago (I believe 10.4), 'allowNaturalLanguage' was marked as deprecated; soon to be removed from the APIs. "What what what‽" said I. I filed a bug: "Why?"&lt;br /&gt;&lt;br /&gt;The response was, essentially, the current heuristic doesn't work perfectly even in English, and fails badly in foreign languages.&lt;br /&gt;&lt;br /&gt;That may seem like a logical reason to remove a piece of API, &lt;em&gt;if you are a programmer.&lt;/em&gt; If you're a user, you're probably thinking, as I did: &lt;em&gt;this is the worst reasoning in the world&lt;/em&gt;.&lt;br /&gt;&lt;br /&gt;Let's say 65% of Mac OS X users speak English primarily. They were all enjoying not having to type dates and times the way the computer wanted. 65% percent of the users were just a &lt;em&gt;little bit&lt;/em&gt; more happy with their experience on Mac OS X. And, &lt;em&gt;crucially,&lt;/em&gt; the other 35% who didn't speak English had &lt;em&gt;no idea what they were missing.&lt;/em&gt; It didn't hurt them at all to not have this functionality, it just didn't help them, either.&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;Life isn't fair, and programming is even less fair. Programming is all about picking a certain class of users with a certain specific class of problems, and making their lives much MUCH better. Like, if I didn't listen to music, I wouldn't care about iTunes. If I didn't take photos of my girlfriends naked, iPhoto would add nothing to my life... but that would be OK by me. iTunes and iPhoto don't have to please everyone in order to be good. They just have to please &lt;strong&gt;some&lt;/strong&gt; people, and should please those people &lt;em&gt;a lot.&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;We talk a lot about the 80% solution, which can be summarized thusly: Will 80% of your users think this feature / heuristic / bug fix is good? Then do it.&lt;br /&gt;&lt;br /&gt;That rule seems obvious, really; the value of this rule is in remembering its obverse: if a feature / heuristic / bug fix is only going to help, say, 20% of your customers, you need to prioritize it lower.&lt;br /&gt;&lt;br /&gt;It's easy when programming to get seduced into doing something really super-duper-cool no matter how obscure, but we have to remember our time is finite: spend your time where your users are going to see it. Do a GREAT job on those areas. (Don't do a shitty job on the other areas - skip them entirely.)&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;In Leopard, '-[NSDateFormatter allowNaturalLanguage]' is no longer marked as deprecated – I won that battle. But there's also a new date widget that makes entering dates a much more graphical affair, which democratizes the happiness. Clear graphics trump heuristic input methods – I use the widget now in my programs, unless I'm parsing text files, in which case I use 'allowNaturalLanguage.'&lt;br /&gt;&lt;br /&gt;Everyone wins, now.&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;So, we switch to another heuristic, which requires a bit of background:&lt;br /&gt;&lt;br /&gt;Amazon recently started requiring that all requests to their product catalog API be digitally signed with the secret password of a registered Amazon associate – one might assume they discovered stealing other people's associate codes is rampant, and they want to crack down on programs and websites that are violating their rules and using their APIs with stolen identities. A reasonable thing.&lt;br /&gt;&lt;br /&gt;I rewrote the lookup code for both Delicious Library 1.7 and Delicious Library 2.2 so they will digitally sign requests, so my customers could keep looking up books and DVDs and stuff. (I don't think there are a lot of 1.x users still out there, but even so I didn't want to force them to pay upgrade to 2.x just to keep using my program.)&lt;br /&gt;&lt;br /&gt;Now, since I've changed how I look up items, immediately after the release of I asked my support team to immediately prioritize all bugs reporting lookup problems, since you modify some area of code you want to alert your support people to actively look for failures in that particular area, so you can fix them immediately instead of waiting until they become a huge issue.&lt;br /&gt;&lt;br /&gt;It turns out there &lt;em&gt;was&lt;/em&gt; a tiny issue (searching for a book by title fails if its title has an apostrophe in it - fixed in 2.2.1) but while looking at user's bugs I discovered something more interesting: a number of users were reporting lookup failures because they were typing in ISBNs the way they see them on the boxes, eg: 978-0-316-01876-0, and they were getting no results, because Amazon stores ISBNs without dashes, eg: 97803780316018760.&lt;br /&gt;&lt;br /&gt;Hrm. Now, my first response to this problem (a year ago when I'd just finished 2.0 and was exhausted) was, "Dammit, just don't type the damn dashes. Who looks up things manually by ISBN anyways? There's like twenty easier input methods, including dragging URLs in from Amazon or scanning with the iSight or typing the author's name... Geez." There was actually a lot more cussing than that, actually, but luckily I have Terry as a buffer layer between me and the customers, so it comes out as, "I'm sorry, we don't accept dashes in ISBNs or EANs right now..."&lt;br /&gt;&lt;br /&gt;But after seeing bug reported again, I realized I'm violating my cardinal rule: I'm making users learn some picky input method that only exists because of Amazon's particular database formatting. And, worse, there's no good way for them to learn this rule unless they write us.&lt;br /&gt;&lt;br /&gt;I should mention that there's only one search field: the user can type in numbers, author names, titles, or even keywords, and we just hand that stuff off to Amazon and let it do a fuzzy search.&lt;br /&gt;&lt;br /&gt;So, how do we solve the dashes issue? Let's run through the solutions we think of, until we hit the best one.&lt;br /&gt;&lt;br /&gt;&lt;table class="codeTable"&gt;&lt;tr&gt;&lt;td class="codeHeader"&gt;Possible Solution 1: Document it&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td  class="codeCell"&gt;Add text near the search field: "Omit dashesWhen entering ISBNs or EANS"&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;br /&gt;Advantages of this method: • It's easy for us to add a field to the NIB. • The user can actually learn from this without having to write us.&lt;br /&gt;&lt;br /&gt;Disadvantages: • We're teaching the user something that's not generally useful, instead of learning from her. • There's another damn text box on or page, which is one more graphical thing calling for the user's attention, and we've learned from bitter experience that every widget you add to a window is like killing a kitten. • This field requires localization.&lt;br /&gt;&lt;br /&gt;&lt;table class="codeTable"&gt;&lt;tr&gt;&lt;td class="codeHeader"&gt;Possible Solution 2: Remove all dashes from input&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td  class="codeCell"&gt;- (IBAction)findMatchingItems:(id)sender;&lt;br /&gt;{&lt;br /&gt;  self.keywordsString = [self.keywordsString stringByReplacingOccurrencesOfString:@"-"&lt;br /&gt;      withString:@""];&lt;br /&gt;  [...search...]&lt;br /&gt;}&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;br /&gt;Advantages of this method: • One line of code, sweet. • User doesn't have to learn anything: ISBNs and EANs can be entered with or without dashes, work either way.&lt;br /&gt;&lt;br /&gt;Disadvantages: • User has lost the ability to search for titles where dashes are meaningful. Eg, "The Mythical Man-Month" would be turned into "The Mythical Manmonth," and Amazon may fail to find THAT, now (actually it does in this instance, but let's not rely on THEIR heuristic working in all cases.).&lt;br /&gt;&lt;br /&gt;&lt;table class="codeTable"&gt;&lt;tr&gt;&lt;td class="codeHeader"&gt;Possible Solution 3: Remove all dashes from input if input is of only digits and dashes&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td  class="codeCell"&gt;- (IBAction)findMatchingItems:(id)sender;&lt;br /&gt;{&lt;br /&gt;  NSString *noDashesString = [self.keywordsString&lt;br /&gt;    stringByReplacingOccurrencesOfString:@"-" withString:@""];&lt;br /&gt;&lt;br /&gt;  BOOL containsOnlyDigits = YES;&lt;br /&gt;  for (NSUInteger characterIndex = 0; characterIndex &lt;&lt;br /&gt;      noDashesString.length; characterIndex++) {&lt;br /&gt;    containsOnlyDigits &amp;= [[NSCharacterSet decimalDigitCharacterSet]&lt;br /&gt;        characterIsMember:[noDashesString characterAtIndex:characterIndex]];&lt;br /&gt;    if (!containsOnlyDigits)&lt;br /&gt;      break;&lt;br /&gt;  }&lt;br /&gt;  if (containsOnlyDigits)&lt;br /&gt;    self.keywordsString = noDashesString;&lt;br /&gt;&lt;br /&gt;  [...search...]&lt;br /&gt;}&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;br /&gt;Advantages of this method: • User doesn't have to learn anything: ISBNs and EANs can be entered with or without dashes, work either way. • User can still search for titles with dashes in them.&lt;br /&gt;&lt;br /&gt;Disadvantages: • User has lost the ability to search for titles that are ONLY digits and dashes, for example, if a user were searching for a book about the latest &lt;strike&gt;quack&lt;/strike&gt; diet, "10-10-10," she'd end up searching for "101010," and maybe not finding it.&lt;br /&gt;&lt;br /&gt;&lt;table class="codeTable"&gt;&lt;tr&gt;&lt;td class="codeHeader"&gt;Possible Solution 4: Remove all dashes from input if input is of only digits and dashes, and the number of digits is right for EANs or ISBNs&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td  class="codeCell"&gt;- (IBAction)findMatchingItems:(id)sender;&lt;br /&gt;{&lt;br /&gt;  NSString * noSpacesOrDashesString = [[self.keywordsString&lt;br /&gt;    stringByReplacingOccurrencesOfString:@"-" withString:@""]&lt;br /&gt;    stringByReplacingOccurrencesOfString:@" " withString:@""];&lt;br /&gt;&lt;br /&gt;  BOOL containsOnlyDigits = YES;&lt;br /&gt;  for (NSUInteger characterIndex = 0; characterIndex &lt;&lt;br /&gt;      noSpacesOrDashesString.length; characterIndex++) {&lt;br /&gt;    containsOnlyDigits &amp;= [[NSCharacterSet decimalDigitCharacterSet]&lt;br /&gt;        characterIsMember:[noSpacesOrDashesString characterAtIndex:characterIndex]];&lt;br /&gt;    if (!containsOnlyDigits)&lt;br /&gt;      break;&lt;br /&gt;  }&lt;br /&gt;  if (containsOnlyDigits) {&lt;br /&gt;    switch (noSpacesOrDashesString.length) {&lt;br /&gt;      case LIISBNDigitCount: case LIUPCDigitCount: case LIEANDigitCount:&lt;br /&gt;        self.keywordsString = noSpacesOrDashesString;&lt;br /&gt;      default:&lt;br /&gt;        break;&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  [...search...]&lt;br /&gt;}&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;br /&gt;(While I was in there, I decided to remove extra spaces if I pass the tests to remove extra dashes, so users can now also type "978 0 316 01876 0" and that will work, as well. It seemed like it might be as common, and it cost me almost nothing to add.)&lt;br /&gt;&lt;br /&gt;Advantages of this method: • User doesn't have to learn anything: ISBNs and EANs can be entered with or without dashes, work either way. • User can still search for titles with dashes in them. • User can still search for titles with only decimal digits and dashes in them, as long as the number of digits doesn't happen to form a valid EAN, UPC, or ISBN.&lt;br /&gt;&lt;br /&gt;Disadvantages: • Kind of reveals that I am a crazy person.&lt;br /&gt;&lt;br /&gt;[Update: several sharp-eyed readers have pointed out I neglect to check for "x" or "X", which is valid as the last digit (the check digit) in the older ISBN-10s. Thank you... I'm human, too!]&lt;br /&gt;&lt;br /&gt;[Update update: Reader Ian Stoba wrote me a note and suggested a more clever (and actually less code for me) heuristic, which is to remove the dashes and check the checksum (last digit) to see if the number that remains is a valid ISBN-10, UPC, or EAN. Since I already have written the methods to do these checks, this is very simple, and pretty fail-proof.]&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;So, that is the algorithm I went with. Let's evaluate this in terms of our goals for any good heuristic (like the one from NSDateFormatter):&lt;ul&gt;&lt;br /&gt;&lt;li&gt;It has to help some class of users – it helps users who type the dashes in ISBNs, UPCs, and EANS, and it helps them a lot, because before they had no clues how to proceed when lookups failed.&lt;br /&gt;&lt;li&gt;It has to not harm other users – it almost never will, because it won't change the input at all unless the user happens to be searching for an author or title that is all numbers and dashes AND has exactly 10, 12, or 13 digits it.&lt;br /&gt;&lt;li&gt;It shows the user what it's doing, so if the heuristic does fail the user will understand why – in this case, we replace the contents of the text field in which the user just typed her number with our new (dashless) number, and so if she really WERE searching for a book whose title was, say, "1-2-3-4-5-6-7-8-9-10-11," she'd see that was replaced by "1234567891011" when she did the search, and at least have a clue why the search failed.&lt;br /&gt;&lt;/ul&gt;--&lt;br /&gt;&lt;br /&gt;Heuristics are the key to designing programs that work well with humans, that make humans smile. In college computer science classes, we learn all about b*trees and linked lists and sorting algorithms and a ton of crap that I honestly have never, ever used, in 25 years of professional programming. (Except hash tables. Learn those. You'll use them!)&lt;br /&gt;&lt;br /&gt;What I do write – every day, every hour – are heuristics that try to understand and intuit what the user is telling me, without her having to learn &lt;em&gt;my&lt;/em&gt; language.&lt;br /&gt;&lt;br /&gt;The field of computer interaction is still in its infancy. Computers are too hard to use, they require us to waste our brains learning too many things that aren't REAL knowledge, they're just stupid computer conventions.&lt;br /&gt;&lt;br /&gt;It's up to us to fix this.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/11049281-4032813207250805473?l=wilshipley.com%2Fblog' alt='' /&gt;&lt;/div&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/4032813207250805473'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/4032813207250805473'/><link rel='alternate' type='text/html' href='http://wilshipley.com/blog/2009/08/pimp-my-code-part-16-heuristics-and.html' title='Pimp My Code, Part 16: On Heuristics and Human Factors'/><author><name>Wil Shipley</name><uri>http://www.blogger.com/profile/04429428424208378747</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='07967666282086327688'/></author></entry><entry><id>tag:blogger.com,1999:blog-11049281.post-4116794592786324162</id><published>2009-05-24T15:50:00.001-07:00</published><updated>2009-05-25T14:52:42.706-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='mac community'/><category scheme='http://www.blogger.com/atom/ns#' term='advice'/><category scheme='http://www.blogger.com/atom/ns#' term='random ideas'/><category scheme='http://www.blogger.com/atom/ns#' term='business'/><title type='text'>Welcome to the iTunes App Store!</title><content type='html'>This document describes our process for reviewing applications for iPhones and iPods touch submitted to the iTunes App Store. We’ve avoided using legalese in this document so that you’ll actually read the whole thing. Please do so &lt;em&gt;before starting to write your application&lt;/em&gt;, so that you won’t waste a bunch of time writing an application that we cannot publish.&lt;br /&gt;&lt;br /&gt;We hate having to reject anyone’s application, so we want to make it clear that we have very compelling motivations behind our acceptance policies, so you can adhere to their spirit. (Oh, our lawyers just reminded us that we should mention that circumstances, times, and social mores change, and we reserve the right to change these policies at any time for any reason, but we’ll do our best to update this document to always reflect the current state of affairs.)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;FIRST&lt;/strong&gt;, you must do no harm. We cannot allow applications that will mess up users’ iPhones, or interfere with their normal operation in any way. The iPhone is a vital communications device, and any downtime on it is unacceptable to Apple and to our customers.&lt;br /&gt;&lt;br /&gt;While viruses, malware, and apps that can be remotely exploited are obvious targets of this rule, there are more subtle implications to this. For instance, we cannot allow applications that send too much data through the cellular network; not only would we be breaking our agreements our cellular carriers (who have priced their data plans based on an estimated average data use of customers &lt;em&gt;without&lt;/em&gt; including your application), but overloading the network would make it unusable for &lt;em&gt;everyone&lt;/em&gt;, and thus violates our first rule.&lt;br /&gt;&lt;br /&gt;We also do not allow emulators or other computer languages right now, because those kinds of applications are notorious for having subtle security holes, and we must take the cautious path to ensure the iPhone remains stable for our customers.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;SECOND&lt;/strong&gt;, if your application is what a reasonable person would consider offensive, we require it to carry an “M” rating on our app store, which carries with it certain restrictions on who can purchase and use your app.&lt;br /&gt;&lt;br /&gt;We do this because, to a certain extent (and despite our best efforts), users do not fully distinguish between third-party authors like yourself and Apple; if something runs on an iPhone and it is ‘bad,’ that is (at least somewhat) considered to be Apple’s fault. Apple abhors censorship in any form, but it also recognizes that even within a single society there are different ideas about what is acceptable and unacceptable, and we would like to warn our customers (yours and Apple’s) who might be more sensitive.&lt;br /&gt;&lt;br /&gt;We realize that any attempt to categorize &lt;em&gt;anything&lt;/em&gt; into “offensive” and “inoffensive” is a fool’s errand, especially considering that your application’s audience will encompass thousands of cultures around the world. Thus, we will use our “best efforts” to determine if an app might be found offensive: for example, if it is overtly sexual, if it contains slurs or curse words, if it has violent themes — these are topics reasonable men disagree on, and our goal is to flag anything that might be controversial so that, for instance, parents can review it with &lt;em&gt;their guidelines&lt;/em&gt; before letting their children see it.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;RESUBMISSION&lt;/strong&gt; of your app can be done if you believe that it has been rejected in error — please include in your resubmission a description of what you’ve changed in your app, or why you believe the original judgment was in error.&lt;br /&gt;&lt;br /&gt;Because in the end our judgments are made by humans, and humans are variable and fickle creatures, our policy is to always let resubmissions be judged by a different person at Apple, to get another perspective.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;REFUNDS&lt;/strong&gt; are opt-in for developers in the iTunes App Store: you can set the number of days during which Apple will offer a full, no-questions-asked refund to your customers, which number will be displayed prominently in your application’s listing on the iTunes App Store and on the user’s list of installed apps inside iTunes.&lt;br /&gt;&lt;br /&gt;While the number is up to you, we generally recommend that the more your application costs, the longer a refund period you offer; if you are making a $20 application and your customers are “done with it” after two days, you may not be offering a good value for money spent. As well, customers will be inclined to trust software that offers a generous refund policy.&lt;br /&gt;&lt;br /&gt;Please note that Apple’s checks to you will be delayed by the length of the refund period you offer; we will not act as your creditor to cover refunds.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;FINALLY&lt;/strong&gt;, note that these rules actually exist only in the fevered and every-hopeful imagination of one Wil Shipley. Consult the real iTunes App Store for its actual policies, not a blog. Dur.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/11049281-4116794592786324162?l=wilshipley.com%2Fblog' alt='' /&gt;&lt;/div&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/4116794592786324162'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/4116794592786324162'/><link rel='alternate' type='text/html' href='http://wilshipley.com/blog/2009/05/welcome-to-itunes-app-store.html' title='Welcome to the iTunes App Store!'/><author><name>Wil Shipley</name><uri>http://www.blogger.com/profile/04429428424208378747</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='07967666282086327688'/></author></entry><entry><id>tag:blogger.com,1999:blog-11049281.post-3622134808016348594</id><published>2008-09-28T15:31:00.001-07:00</published><updated>2008-09-30T15:59:08.676-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='random ideas'/><title type='text'>Tesla v. Supercharged Lotus Elise</title><content type='html'>&lt;h4&gt;&lt;center&gt;Or, &lt;i&gt;I Test-Drove a Tesla and All I Got Was These Lousy Adrenaline Shakes&lt;/i&gt;&lt;/center&gt;&lt;/h4&gt;&lt;br /&gt;&lt;b&gt;Short version:&lt;/b&gt; Tesla.&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Long version:&lt;/b&gt; I've had insomnia since I was a wee lad, and at night I often calm my brain by thinking up idealized objects. Optimal house cooling, unpowered water purification, the ultimate graphics card, the coolest car &lt;i&gt;ever&lt;/i&gt;. It's a good exercise, I think: if you don't have an idea of what you'd like something to become, you won't ever get it there.&lt;br /&gt;&lt;br /&gt;Some of these crazy dreams have come true, which is particularly disturbing. When I was 10 or so I thought up a crazy idea for fonts that would be described by curves, and rendered in real-time by the graphics card at the best possible resolution, instead of having hand-tuned bitmaps. Of course, I was 10, so I had no idea what the math would be, I just figured, THIS would be the coolest, ultimate form of fonts. A few years later PostScript was in printers, and a few years after that Display PostScript hit the desktop, then TrueType, and now nobody even remembers bitmap-only fonts.&lt;br /&gt;&lt;br /&gt;Also as a kid I'd try to think up the ultimate car... What if you had no gearbox - you just had one gear and an electric motor that could spin as fast as you wanted directly driving the wheels, so the mapping between the accelerator pedal and your speed would be practically linear &amp;mdash; the way kids imagine &lt;i&gt;all&lt;/i&gt; cars work until they actually learn to drive and have to figure out gears and clutches and all that.&lt;br /&gt;&lt;br /&gt;But, wait, this ideal car would have so much power, you'd end up jump off the road if you accidentally used the slightest bit too much pressure on the accelerator. Oh, I know, the car would have a computer controlling the wheels, and it'd detect when you started to slip and automatically compensate. But, hold on again, isn't the fun in driving being able to burn out and spin around and stuff? Maybe we need the ability to disable the computer, sometimes.&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;And, now, again: &lt;i&gt;Tesla&lt;/i&gt;. Here it is; the crazy dream-car that a kid might design in his naivety of how the world really works, except some engineers didn't get the memo on what's possible, and went ahead and built it.&lt;br /&gt;&lt;br /&gt;Tesla.&lt;br /&gt;&lt;br /&gt;It's crazy-fast. It handles like a jet fighter. It gets the equivalent of about 140 mpg. It has no gears. It requires almost no maintenance.e It's gorgeous. It's whisper-quiet. And, in Seattle, runs off hydro power.&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;So, yes, I test-drove a Tesla today, for five laps on a closed course in the parking lot of a defunct K-mart. Then I took my supercharged Lotus Elise around the same course. (The Tesla I drove was an engineering prototype but is said to be very close to the one I'll get next year.)&lt;br /&gt;&lt;br /&gt;I expected the Tesla to have more power than the Elise (based on the Tesla's 0-60 of about 4 seconds, as opposed to the supercharged Elise's estimated 4.5-7-ish), and it didn't disappoint.&lt;br /&gt;&lt;br /&gt;The first part of the course was a straight, wide-open acceleration towards two tiny cones, which were where you were supposed to brake "as hard as you can" so you didn't end up flying out of the lot and into the street. It turns out they were conservative on this by a bit; by my fifth lap I'd found the Tesla could brake down enough to make the turn in about half the space they'd given me. The car was nimble.&lt;br /&gt;&lt;br /&gt;And, compared to the Lotus, the Tesla seemed faster to the cones; at first not by a huge amount, but the huge difference came when the Lotus hit the top of its first gear it started stuttering from the electronic limiter, and I had get out of gear and shift. The Tesla just kept accelerating the whole time, so by the time it hit the cones it was going WAY faster.&lt;br /&gt;&lt;br /&gt;Remember: this car is NOT an automatic: there are no damn gears. It's not shifting for you; there's no annoying pauses where the car decides what gear you might want, right in the middle of your burn. The engine is basically hooked up directly to the damn wheels. It's amazing. It has that responsive feeling of when you are driving a normal car in first gear, except it has that no matter how fast you are going.&lt;br /&gt;&lt;br /&gt;Taking the 'S' cones in the Tesla, on the second or third lap I was finally able to get the tires to chirp if I turned at the highest speed I could get out of the turn. But the car never slid out of control, even a little bit. The nose was always pointed where I wanted, and the back tires were always ready to accelerate.&lt;br /&gt;&lt;br /&gt;This was a huge advantage of the Tesla; I'm not a race-car driver, and I admit that it's pretty hard for me to take a sharp turn at high speed AND be shifting in the Elise. Hell, it's enough hard to hold the damn steering wheel with &lt;i&gt;two&lt;/i&gt; hands at that speed. In the Tesla, at any point during a turn I could tap the accelerator and I was (a) guaranteed to be in gear, and (b) guaranteed to be in the RIGHT gear. Because, you know... there's only one gear, and it was "fast" gear.&lt;br /&gt;&lt;br /&gt;Call me a wuss if you like, but I found it a lot more fun to be concentrating on just pointing the wheel and calculating the amount of slide, and NOT trying to shift and steer and clutch all at once. I'm just not that good.&lt;br /&gt;&lt;br /&gt;The big surprise from the Tesla was when I took my Elise through the same course. The Elise is several hundred pounds lighter and has stickier tires on the front and the same on the rear. &lt;i&gt;And the Elise slid around the cones out-of-control.&lt;/i&gt; I'd take a cone at high speed and the Elise would start jump-skipping its tires sideways, and steering and acceleration were offline until it'd stop. I'd lose momentum and end up kind of off-course.&lt;br /&gt;&lt;br /&gt;&lt;img src="http://wilshipley.com/blog/lotus.png" alt="The Lotus - no pictures in the Tesla, sadly." width=470 height=116 /&gt;&lt;br /&gt;&lt;br /&gt;I don't really have a good explanation for why the Elise handled worse for me than the heavier Tesla. Maybe having precise control of the accelerator throughout turns enabled me to keep the tires spinning in the road-wise direction? Maybe the Tesla's slightly longer wheelbase or different weight distribution made a big difference? Maybe the traction control on the Tesla is really that good? I don't know.&lt;br /&gt;&lt;br /&gt;I do know that, going around the cones as fast as I could, the Elise lost control sliding sideways for a little bit on both laps, where the Tesla merely chirped her tires and drove on.&lt;br /&gt;&lt;br /&gt;Normally, features like "traction control" or "no shifting" put me off of a car, because they essentially translate to "low-performance-idiot-mode." You turn them on and you feel like the car is driving and you're a passenger. I've hated 'em in Ferraris and Mercedes SLs.&lt;br /&gt;&lt;br /&gt;With the Tesla, these features are put in to allow you to drive &lt;i&gt;harder&lt;/i&gt; and &lt;i&gt;faster&lt;/i&gt; and still feel completely in command. *I* am driving. I feel the road. I feel the wheels.&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;The only drawbacks I found were: the Tesla seemed a little slow immediately off the line - full-throttle starts have a slight pause, and THEN you are pressed back against your seat. I guess this is the price you pay for the One Gear also allowing you to go 100 mph (or so). The other drawback is that, yah, the Tesla really doesn't want to maintain a speed above 100 mph for very long. It just isn't designed for that.&lt;br /&gt;&lt;br /&gt;Sure, I don't drive about 100 very &lt;i&gt;often&lt;/i&gt;, but, you know &amp;mdash; sometimes it's nice. Not a deal-breaker, though.&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;In Seattle, I've been told a law just got passed where we pay no sales tax on very efficient vehicles, so I avoid the ~9% extra charge. Also, it looks as though the president will sign into law a bill that gives a $7,500 tax &lt;i&gt;credit&lt;/i&gt; (NOT a deduction, but a full CREDIT), to people that buy a car with a battery at least as big as the one in the upcoming Chevy Volt, and the Tesla happens to qualify as well.&lt;br /&gt;&lt;br /&gt;This doesn't make the Tesla exactly cheap, but it's sure nice to save $16,000.&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;The Tesla people were uniformly cool and &lt;i&gt;real&lt;/i&gt; and fun. During the open house there wasn't a single question they dodged. They offered up problems they'd found, troubles they'd had getting into production, issues that loom on the horizon.&lt;br /&gt;&lt;br /&gt;They test-drove the car themselves because they still all love it so much. One guy turned off traction control (we were not allowed to) and demonstrated how much power the car really has &amp;mdash; it was, frankly, daunting, and he got waved down after two laps because the guy in charge thought he was going to power-slide into the defunct K-mart. (A very real possibility.)&lt;br /&gt;&lt;br /&gt;My friend commented as we left how nice the Tesla executives were. I am excited about this company, again. Excited to be little tiny part of a team that is so committed to changing the world AND having fun. Excited to have a dealer that is a "company store," where the salespeople aren't on commission, and really &lt;i&gt;want&lt;/i&gt; to help you love their product.&lt;br /&gt;&lt;br /&gt;I like to spend my money with companies whose philosophies align with mine: in my life I've only made one purchase at Wal*mart, I don't eat Domino's pizza, I have vegetables delivered from small organic local farms, etc, etc, blah blah blah. My point is, I am *happy* to give these guys my money.&lt;br /&gt;&lt;br /&gt;I am happy to have a chance to say, with my dollars, "You guys are doing the right things, and I support you."&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;For sale: Ardent Red 2005 Lotus Elise, with aftermarket supercharger. One driver, excellent condition.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/11049281-3622134808016348594?l=wilshipley.com%2Fblog' alt='' /&gt;&lt;/div&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/3622134808016348594'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/3622134808016348594'/><link rel='alternate' type='text/html' href='http://wilshipley.com/blog/2008/09/tesla-v-supercharged-lotus-elise.html' title='Tesla v. Supercharged Lotus Elise'/><author><name>Wil Shipley</name><uri>http://www.blogger.com/profile/04429428424208378747</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='07967666282086327688'/></author></entry><entry><id>tag:blogger.com,1999:blog-11049281.post-400652803418999982</id><published>2008-09-22T16:26:00.000-07:00</published><updated>2008-09-23T16:17:22.926-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='mac community'/><category scheme='http://www.blogger.com/atom/ns#' term='business'/><title type='text'>iPhone App Store: Let the Market Decide</title><content type='html'>Call me a proponent of free markets, but I think Apple needs to have a clearly-documented policy for approving submissions to the iPhone App Store, and it should be:&lt;br /&gt;&lt;br /&gt;• &lt;i&gt;Publish &lt;/i&gt;all&lt;i&gt; software submitted to Apple, as long as the software isn't actively harmful to users, illegal, and does not violate Apple's agreements with cell phone vendors.&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;• &lt;i&gt;Period.&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;The iPhone app store is, at heart, incredible. Every software developer's dream store looks a lot like this:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;100% of the devices that can run my software have a link directly to this store.&lt;br /&gt;&lt;li&gt;And only this store.&lt;br /&gt;&lt;li&gt;And the user can't remove the link to this store from the device.&lt;br /&gt;&lt;li&gt;There's no other way to buy software, so users are never confused as to whether they should go to some website or physical store or the online store to find software for their devices.&lt;br /&gt;&lt;li&gt;Users never wonder if there's some other, better software out there - if it's not on the store, it doesn't exist.&lt;br /&gt;&lt;li&gt;Users can buy with a click.&lt;br /&gt;&lt;li&gt;Software is instantly installed and enabled for users.&lt;br /&gt;&lt;li&gt;"Good enough" copy-protection is handled invisibly for all developers.&lt;br /&gt;&lt;li&gt;Apple hosts the software makes the pretty, professional website.&lt;br /&gt;&lt;li&gt;Credit card transactions are handled automatically.&lt;br /&gt;&lt;li&gt;Apple's percentage is MUCH lower than traditional distribution.&lt;br /&gt;&lt;li&gt;The store actually pays out the money it owes you, unlike the vast majority of physical distributors.&lt;br /&gt;&lt;li&gt;There's already a market of something around ten million users for iPhone apps.&lt;br /&gt;&lt;li&gt;The market is increasing by the day.&lt;/ul&gt;&lt;br /&gt;That's a LOT of plusses. A LOT. And it's working. Developers are reporting making thousands to hundreds of thousands of dollars every MONTH and the store is only a couple months old.&lt;br /&gt;&lt;br /&gt;Some of this is likely because of the novelty of the device and the store itself &amp;mdash; there's a mini-gold rush effect happening, and already I suspect that if you weren't one of the guys to get rich selling a flashlight app or sudoku, well, you probably shouldn't start writing one now.&lt;br /&gt;&lt;br /&gt;So, yay Apple, and yay developers who are rolling in fat, filthy lucre. (I'm not bitter that I didn't get in on that first round. No, no.)&lt;br /&gt;&lt;br /&gt;As with any pioneering effort that succeeds (c.f. Twitter's constant whale-fails when it took off), Apple is encountering problems it never anticipated, and having to make up solutions on-the-fly.&lt;br /&gt;&lt;br /&gt;Which is fine, and good, except, well... maybe we developers need to give Apple a loving nudge, so the problems are solved in a conscious way that helps everyone, instead of being solved ad-hoc and turning into policies which punish us all.&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Problem: Most Software is Crap&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;There's a LOT of crap out there for the iPhone. A LOT. And a bunch of neat apps. How does the the user tell them apart? Should Apple's model be like Nintendo, where Apple only allows software through that meets their rigorous standards for being fun and cool and stable? It sounds nice, except (a) it requires a ton of effort on Apple's part, and Apple's success or failure is determined entirely by the tastes of the people doing the vetting, and (b) it stifles innovation. (Look at the number of titles available for the Nintendo Wii, which has been out for years, vs. the number for the iPhone, whose SDK has been available for months.)&lt;br /&gt;&lt;br /&gt;The other problem with Apple vetting apps for quality is that Apple gets blamed if crappy apps slip through the process. Once you appoint yourself censor, you've taken responsibility. If an App Store app crashes, it'll be blamed on Apple. If an App Store app has a bug, it'll be blamed on Apple. If an... well, you see where I'm going.&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;Recently Apple decided to go ice-skating on the slippery slope of censorship by removing the "I am Rich" application from its store. Briefly: some prankster priced an app at $999 that did nothing but show some text and a picture, congratulating the purchaser for being rich and stupid. Apple pulled the app after a few days, citing "not enough functionality" or some such.&lt;br /&gt;&lt;br /&gt;Now, this application did point real problems in the &lt;i&gt;system&lt;/i&gt;, but not in the &lt;i&gt;app&lt;/i&gt;. The problems are in the App Store, and they are: it's not really clear how to get refunds, and it's a little TOO easy to click on something that says "$999" without realizing that, seriously, this is a grand you're blowing.&lt;br /&gt;&lt;br /&gt;Let's solve the &lt;i&gt;real&lt;/i&gt; problems so that we don't &lt;i&gt;need&lt;/i&gt; to censor apps, and so that developers don't need to guess if their apps are "functional" enough to pass muster with whichever App Store censor they happen to get:&lt;br /&gt;&lt;br /&gt;• Apple needs to have a clearly posted refund policy that applies across-the-board. They may already have a policy, but, honestly, I've bought 15 or so apps and I've never seen it, and I'm going to say that if users don't see the policy, you might as well not have it.&lt;br /&gt;&lt;br /&gt;I'd suggest something like, "You can get a full refund any time in the first two weeks of ownership of any app." This would solve many problems: if the app turns out to be buggy, or have limited functionality, or insult your mom, or whatever... well, it's not Apple's problem any more. They refund your money and everyone's happy.&lt;br /&gt;&lt;br /&gt;• For apps over some threshold ($30? $100?), Apple needs to add a click to the purchase process. Something like, "Note: this is A HUNDRED REAL LIVE SMACKERS, here, so MAKE SURE you really want this, OK?"&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;After that first rejection, there have been two more reports of rejections. I can't verify them myself, of course, but I also have no reason to doubt the reports. One of these applications had 'podcasting' as part of its functionality, and one had fetching mail from Google as part of its functionality.&lt;br /&gt;&lt;br /&gt;Both were censored because of a new criterion Apple has invented, which is "duplicates existing functionality." Let me make my position on this perfectly clear: it in unethical and antithetical to the whole IDEA of an App Store for Apple to be censoring applications based on criteria they have &lt;i&gt;never&lt;/i&gt; given to developers, and only told developers &lt;i&gt;after&lt;/i&gt; the developers put in all the work of writing an app.&lt;br /&gt;&lt;br /&gt;Even TV network censors produce a "standards and practices" document, so writers can tell if they are pushing the envelope. Apple's censors have acted capriciously and against the interests of all of its developers, its customers, and itself.&lt;br /&gt;&lt;br /&gt;This situation is worsened because it's obvious that Apple is only worried about applications duplicating the functionality of &lt;i&gt;Apple&lt;/i&gt;'s iPhone applications &amp;mdash; there are twenty "sudoku" apps and a dozen "flashlights" and a bunch of pokers and, heck, there's more than one racing game.&lt;br /&gt;&lt;br /&gt;But it was only when a developer added functionality that Apple considered sacrosanct to &lt;i&gt;Apple itself&lt;/i&gt; that she was censored. Apple wasn't worried about customer confusion, Apple was worried about getting some competition.&lt;br /&gt;&lt;br /&gt;I have to be clear: it simply will not stand for Apple to prevent applications on the iPhone from competing with Apple's own applications. Besides chasing away all decent developers, besides hurting their customers by stifling competition and innovation, besides it simply being evil, it will, shortly, be illegal. This kind of behavior is illegal when you hit a certain point in market saturation for your product; Microsoft was slapped for it constantly in the late '80s. If the iPhone is the success Apple thinks it will be, they will find themselves the target of a huge class-action lawsuit.&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;I can see how the iPhone App Store could be some short-sighted Apple marketing dude's dream: "Hey, we can nip all competing applications in the bud and completely own any market we choose! Imagine how well Final Cut Pro would do without Premiere! Imagine iPhoto without Lightroom! We own it all, baby!"&lt;br /&gt;&lt;br /&gt;Those of us who actually write software know that, in fact, killing your competition is a sword that's not just double-edged, but in fact has a blade as its handle, as well. &lt;i&gt;Without competition there is no innovation.&lt;/i&gt; Apple &lt;i&gt;needs&lt;/i&gt; competing apps. As they add features or speed or UI innovations, Apple can copy them and make Apple's apps better.&lt;br /&gt;&lt;br /&gt;Competition is how nature has made strong organisms since literally the beginning of time. You simply won't get stronger if you don't have adversity. It is demonstrated in any system you can think of, from virus resistance in operating systems to the relative strength of the huns versus the northern Chinese.&lt;br /&gt;&lt;br /&gt;There's a simple proof of why competing apps should exist: (1) If customers use the third-party app, it clearly provides some functionality Apple's version does not, and customers benefit and the platform is stronger. (2) If customer do not use the third-party app, that app withers and dies and nobody is hurt.&lt;br /&gt;&lt;br /&gt;But, ignoring how Premiere actually helps Final Cut, let's imagine a world in which Apple DID censor Premiere and Lightroom for "duplication [Apple's] existing functionality." What do you think Adobe would do with Photoshop? Flash? InDesign?&lt;br /&gt;&lt;br /&gt;If you voted, "Make those suckers Windows-only," give yourself a gold star. Now think about how not having those applications would have affected where the Mac market is today. (Remember the lag in selling Intel machines until Adobe made Photoshop "Universal?" Imagine if it didn't run &lt;i&gt;at all&lt;/i&gt;.)&lt;br /&gt;&lt;br /&gt;Now imagine the next revolutionary application for phones, and what platform it's going to be on if Apple doesn't cut this crap out. (Hint: rhymes with "manbloid.")&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;"What about all the crap-ware? Aren't decent applications getting buried be all the stuff that's just being dumped out there in hopes of a few pity clicks?"&lt;br /&gt;&lt;br /&gt;This is actually surprisingly easy to solve. Eventually, there are going to be tens and tens of thousands of apps on the App Store. Just simply paging randomly through applications to find one is already far too onerous to be practical.&lt;br /&gt;&lt;br /&gt;The App Store needs to think of itself as two different parts - it already implements these parts, but the people who run the store need to understand that &lt;i&gt;these two parts are fundamentally separate&lt;/i&gt;:&lt;br /&gt;&lt;br /&gt;• Part one is a giant warehouse, where every piece of software that is &lt;i&gt;not actively harmful&lt;/i&gt; is kept in case someone wants to buy it (remember, users can always get a refund). This warehouse can be searched with titles and keywords or an item can be directly linked.&lt;br /&gt;&lt;br /&gt;• Part two is like a traditional storefront, with limited real estate, so only the best or coolest applications are highlighted. It's a recommendation engine, that highlights popular, highly-rated, or innovative applications.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;Everyone&lt;/i&gt; can get into the warehouse. Only the select few can get into the storefront.&lt;br /&gt;&lt;br /&gt;Customers win because they can choose whatever software they like, regardless of whether Apple "approves" of their choice or not. Apple wins because developers aren't alienated and don't all go develop for Android, and so Apple has the device where all the innovation is happening. And developers win because the obviously cool apps will be featured by Apple and get tons of his, but even if their app isn't "blessed" by Apple, if it's a neat enough idea it'll become popular on its own, through word-of-mouth.&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;It's a huge mistake for Apple to appoint themselves arbitrator of what's cool, or to even &lt;i&gt;appear&lt;/i&gt; to do so. It's an equally huge mistake for Apple to decide that all innovation must come from Apple.&lt;br /&gt;&lt;br /&gt;Let's list a handful of cool Apple apps: Safari. iTunes. Preview. Mail. iSync.&lt;br /&gt;&lt;br /&gt;Did Apple invent the ideas or protocols behind any of these? Nope. Did Apple write the first implementations? Nope. Did Apple even write the original code they are using for their versions? Nope. (They licensed them all from third parties, except for Mail.)&lt;br /&gt;&lt;br /&gt;When the next cool app comes out, and the next one after that, is it going to be on the iPhone, or on Android? It's really Apple's call.&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;UPDATE 9/23: Apple's response is reportedly to &lt;a href="http://www.macrumors.com/2008/09/23/apple-extends-non-disclosure-to-app-store-rejection-letters/" target="other"&gt;put the rejection letters under nondisclosure&lt;/a&gt;, as well. That's, uh, not a solution, guys. In fact, it's the opposite. It makes you look more draconian. And it's a useless gesture.&lt;br /&gt;&lt;br /&gt;Do you REALLY think developers are not going to talk among ourselves, or leak info to the press, after we've worked for months on an application and then had it capriciously rejected by Apple? All the press has to say is, "we've heard of several developers who have been rejected" and there's nothing you can do; you can't subpoena people who aren't under NDA, and you won't know who among your NDA'd rejectees talked.&lt;br /&gt;&lt;br /&gt;Seriously, Android is open and free. The tighter you try to clench your fists, the more developers you are going to drive away. Yes, you have the nicest frameworks and the prettiest hardware. But that's only the first part of what you need.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/11049281-400652803418999982?l=wilshipley.com%2Fblog' alt='' /&gt;&lt;/div&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/400652803418999982'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/400652803418999982'/><link rel='alternate' type='text/html' href='http://wilshipley.com/blog/2008/09/iphone-app-store-let-market-decide.html' title='iPhone App Store: Let the Market Decide'/><author><name>Wil Shipley</name><uri>http://www.blogger.com/profile/04429428424208378747</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='07967666282086327688'/></author></entry><entry><id>tag:blogger.com,1999:blog-11049281.post-4125953378764058135</id><published>2008-07-29T21:10:00.000-07:00</published><updated>2008-07-29T22:23:12.670-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='mac community'/><category scheme='http://www.blogger.com/atom/ns#' term='stories'/><category scheme='http://www.blogger.com/atom/ns#' term='business'/><title type='text'>“The Mojave Experiment:” Bad Science, Bad Marketing</title><content type='html'>I guess I should first admit I hate the show &lt;i&gt;Punk’d&lt;/i&gt;. I mean, here’s a guy who is famous for lying about his age so he seems hipper, telling us that his show’s purpose it to deflate the big egos on other stars, and show them what truly matters in life. So he sets up situations where anyone would get upset, and then laughs when he upsets people. I call *cough*bullshit*cough*. (Also *cough*jerkface*cough*.)&lt;br /&gt;&lt;br /&gt;So I have to admit I’m not predisposed to like &lt;a href="http://www.mojaveexperiment.com/" target="other"&gt;The Mojave Experiment&lt;/a&gt;, where Microsoft took a bunch of “regular folks” XP users who were afraid of Vista, and told them Microsoft was going to show them a secret new operating system &amp;mdash; which was actually Vista.&lt;br /&gt;&lt;br /&gt;UNSURPRISINGLY, these people mostly said they liked Vista.&lt;br /&gt;&lt;br /&gt;Now, if you read this blog, you know I pretty much hate Microsoft, because of their incredibly shady business practices (moreso in the early 1990s) and their shoddy products, most especially their operating systems, whose crappy user experience and programmer interfaces hold back the advance of technology. However, I’m not going to rail on Vista here. Seriously, I’m not.&lt;br /&gt;&lt;br /&gt;What I &lt;i&gt;am&lt;/i&gt; going to rail on is this “experiment.” (I use that word advisedly.)&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;I &lt;b&gt;hate&lt;/b&gt; bad science. Hate it. Hate. So let’s look at not one, not two, but FOUR, yes FOUR (ah-ah-ah!) key flaws in this experiment, any single one of which would render its results meaningless:&lt;br /&gt;&lt;br /&gt;• &lt;b&gt;The Placebo Effect:&lt;/b&gt; Every time I do a software release, no matter how minor, even if I just change one word, in French, to another French word, someone will send me mail or post on a forum, “Thanks, this release seems a lot faster!” Do I make fun of them? Or videotape them and put it on a blog? No. Because it’s just human nature. If we are told something is new-and-improved, we prime ourselves to believe it (c.f. &lt;i&gt;&lt;a href="http://www.amazon.com/Blink-Power-Thinking-Without/dp/0316172324" target="other"&gt;Blink&lt;/a&gt;&lt;/i&gt; by Malcolm Gladwell, which I’ll refer to again in a bit) and make it so in our minds.&lt;br /&gt;&lt;br /&gt;This is why we have, for example, blind taste tests: because humans are proven to not be able make dispassionate judgments about subjects they already know about. So, if you say to someone, “Hey, I’m giving you a top-secret peek at a new operating system from Microsoft, you’re incredibly lucky and special, and I really value your opinion!” of COURSE they are going to like it. They almost can’t not like it.&lt;br /&gt;&lt;br /&gt;• &lt;b&gt;The Pepsi Challenge Effect:&lt;/b&gt; “The Pepsi Challenge” was a blind taste test that Pepsi overwhelmingly won (again, from Blink). Yet, most people still drink Coke. Why? Gladwell’s thesis is that a &lt;i&gt;single sip&lt;/i&gt; of a soft drink is very different from drinking a &lt;i&gt;whole can&lt;/i&gt;, which is the smallest unit most people imbibe. Pepsi usually wins the challenge because it's a sweeter drink, and initially people respond to this extra sweetness. But after drinking a can, Pepsi becomes cloying.&lt;br /&gt;&lt;br /&gt;So, here I am, sat down in front of Mojave-err-Vista, and all I've ever used is XP. Well, look, nobody is doubting the graphics are prettier in Vista. It looks nice compare to XP (it &lt;i&gt;should&lt;/i&gt; &amp;mdash; they hired the guy who designed Aqua for Mac OS X).&lt;br /&gt;&lt;br /&gt;I play with Mojave, and, yes, some system tasks are easier. Again, nobody doubts there are things that work much better. When I plug my iSight camera into Vista it shows up as a device and offers to let me take pictures in the Vista Explorer thingy. That’s kind of cool! Hey, I kind of like Mojave-nee-Vista!&lt;br /&gt;&lt;br /&gt;Except, &lt;i&gt;those glossy features aren’t why people downgrade from Vista to XP.&lt;/i&gt; Those are &lt;b&gt;not&lt;/b&gt; the reason people hate on Vista!&lt;br /&gt;&lt;br /&gt;Now, again, look &amp;mdash; I don’t use Vista or XP for anything but games. I liked using Vista better, until the new UFO (X-Com) game that I had played great on XP, and wouldn’t launch at all on Vista. Then I bailed. That’s my story. There are apparently hundreds of others.&lt;br /&gt;&lt;br /&gt;You, personally, may never have encountered a piece of hardware or an app that didn’t work on Vista, and you might be perfectly happy with it. I’m not going to try to argue you out of that happiness. My point is that &lt;b&gt;the problems that Vista has become famous for are not the kinds of problems you encounter in a few minutes of playing with it in a controlled environment.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Vista is known for people initially &lt;i&gt;liking it&lt;/i&gt;, then after a while discovering it’s not working for them, and “downgrading” to XP. This study has told us exactly what we already knew: that, &lt;i&gt;initially&lt;/i&gt;, people like Vista. (&lt;i&gt;Initially&lt;/i&gt;, people like having sex without condoms, too... it’s simply not a very good criterion all by itself.)&lt;br /&gt;&lt;br /&gt;• &lt;b&gt;The Perfectly Controlled Environment Effect:&lt;/b&gt; Microsoft set up the hardware. Microsoft brought the accessories. Microsoft picked the software. Microsoft sat people down with Vista experts driving the mouse, and walked people through Vista. What an INCREDIBLE SHOCKER that in this INCREDIBLY TIGHTLY CONTROLLED ENVIRONMENT Vista performed OK!&lt;br /&gt;&lt;br /&gt;Microsoft had set up an environment with a philosophy similar to Apple’s: “Look, we work well with this hardware and software, and too bad if you want something different.” Unfortunately, that’s NOT why people choose Windows. They hack together their own machines, and they want their software to still run.&lt;br /&gt;&lt;br /&gt;Did &lt;i&gt;any&lt;/i&gt; of these customers bring in their favorite games and try to play them? Did they bring in their graphics tablets and discover they fail?&lt;br /&gt;&lt;br /&gt;Did &lt;i&gt;any&lt;/i&gt; of the test machines ever say, “Oh, I’m sorry, Windows Genuine Advantage has determined that you may be running an invalid copy of Windows, so please jump through these hoops or we’ll disable some of your hardware”? I’m going to guess no. But I’ve seen this message a lot. And I own three valid licenses to Windows.&lt;br /&gt;&lt;br /&gt;• &lt;b&gt;The Personal Tutor Effect:&lt;/b&gt; If you sit &lt;i&gt;anyone&lt;/i&gt; down with an expert in a particular program, and the expert walks them through the features and answers their every question, chances are good that person is going to report that she had a good experience with the program. Very good, indeed.&lt;br /&gt;&lt;br /&gt;Personal training is so important to customer experience that Apple thinks of it as a key asset of its Apple Stores. But Microsoft doesn’t have Apple Stores in real life. Or any analog. It’s you and a box with a holographic sticker on it. Good luck!&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;Microsoft has managed to prove that if you have a friendly expert on a controlled machine (with Vista pre-installed) showing a carefully selected subset of Vista features to an ignorant XP user for a few minutes, the XP user will often say he finds Vista acceptable. Wow.&lt;br /&gt;&lt;br /&gt;This so-called experiment of Microsoft’s is an insult to science, and to our intelligence. And I am &lt;i&gt;dying&lt;/i&gt; to see the out-takes from their shoot. I mean, how many people do you suppose like being told, “Hey, this giant, unpopular monopolistic software company just made an ass out of you! Ha ha! Our leading scienticians just PROVED that you LOVE VISTA and WANT TO MARRY IT. You are TOTALLY GAY for Vista! Haaaaaaa HAAAAAAA!”&lt;br /&gt;&lt;br /&gt;Vista may or may not be an upgrade in user experience for most Windows customers. I personally prefer the feel of Vista over XP when the former works as well as the latter, but Vista has failed me on several occasions, and I also don’t enjoy running games MORE slowly than XP.&lt;br /&gt;&lt;br /&gt;I've got to imagine that the Microsoft customers who took all the damn time to upgrade their machines to Vista, determined it was unworkable, and then had to take all the time to go BACK to XP, &lt;b&gt;probably&lt;/b&gt; did so for a reason, possibly even a &lt;i&gt;valid&lt;/i&gt; reason, and not because they had been swayed by bad word-of-mouth. I further imagine that these customers are &lt;i&gt;completely livid&lt;/i&gt; at having Microsoft not say, “Oh, sorry, we’ll get right on those bugs,” but, instead, “You’re just stupidly following the crowd, and if you’d just free your mind up, you’ll discover you actually love Vista... hater.”&lt;br /&gt;&lt;br /&gt;Is “Our Customers Are Stupid and Have No Idea What They Really Want” really Microsoft’s new mantra?&lt;br /&gt;&lt;br /&gt;Again, wow.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/11049281-4125953378764058135?l=wilshipley.com%2Fblog' alt='' /&gt;&lt;/div&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/4125953378764058135'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/4125953378764058135'/><link rel='alternate' type='text/html' href='http://wilshipley.com/blog/2008/07/mojave-experiment-bad-science-bad.html' title='“The Mojave Experiment:” Bad Science, Bad Marketing'/><author><name>Wil Shipley</name><uri>http://www.blogger.com/profile/04429428424208378747</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='07967666282086327688'/></author></entry><entry><id>tag:blogger.com,1999:blog-11049281.post-239909600890133322</id><published>2008-07-07T17:15:00.001-07:00</published><updated>2008-07-07T17:19:37.288-07:00</updated><title type='text'>Pimp My Code, Part 15: The Greatest Bug of All</title><content type='html'>Last week, a customer reported a bug to me in &lt;a href="http://delicious-monster.com/" target="_other"&gt;Delicious Library 2&lt;/a&gt;: when he first launched version two, his version one data would start to be imported, but after waiting for tens of minutes watching the annoying aqua progress bar creep along, his entire machine would crash. Every time.&lt;br /&gt;&lt;br /&gt;His collection was so huge it took me two solid days to download his version one files onto my machine. When I ran Delicious Library 2 on my Air in debug mode, it converted his data for about thirty minutes, then crashed with an exception that said the database file was 'corrupt'. I tried to look where it had crashed, but XCode was reporting that &lt;i&gt;all my source code files were gone.&lt;/i&gt; In fact, so were my iTunes songs, and everything in my Documents folder. I opened Finder and Terminal, and verified that the directories were indeed empty. No error messages, no residual files. Just empty. Panic.&lt;br /&gt;&lt;br /&gt;A minute or so later, all these files magically came back. Everything. As if nothing had happened.&lt;br /&gt;&lt;br /&gt;After thinking about this overnight (while staying away from the computer), I changed one word in my source code, and the customer's file loaded perfectly, in only twenty minutes.&lt;br /&gt;&lt;br /&gt;&lt;center&gt;&lt;b&gt;Part 1: Triage&lt;/b&gt;&lt;/center&gt;&lt;br /&gt;In my personal pantheon of bugs, this report was triaged at the top. Roughly, my triage order is:&lt;ul&gt;&lt;li&gt;Data-loss bugs&lt;br /&gt;&lt;li&gt;Unavoidable crashers&lt;br /&gt;&lt;li&gt;Functionality-blocking bugs&lt;br /&gt;&lt;li&gt;Avoidable crashers&lt;br /&gt;&lt;li&gt;Avoidable bugs&lt;br /&gt;&lt;li&gt;Misfeatures&lt;br /&gt;&lt;li&gt;Performance issues&lt;br /&gt;&lt;li&gt;Feature suggestions&lt;br /&gt;&lt;li&gt;UI feedback&lt;br /&gt;&lt;/ul&gt;This is, of course, a rough ordering: obviously if a customer is trying to publish a single item to her website and it's taking a half hour, then I'm obviously going to bump that up the list over someone whose app crashes when she leans on the "a" key for a three hours in the "Actors" field. (For the record, neither of those are real bugs.)&lt;br /&gt;&lt;br /&gt;But, note that in the first case, the "performance" issue has made the feature essentially unusable, so it's really a functionality-blocking bug. Further, if publishing is synchronous, then this bug is blocking access to the application for an unacceptable amount of time, and could be considered a crasher of sorts.&lt;br /&gt;&lt;br /&gt;Weighing the bug my real user reported, we see that it's The Perfect Storm, a trifecta that crashes the app (and the &lt;i&gt;machine&lt;/i&gt;!), every time (on launch!!), blocks all functionality, and, to add insult to injury, it takes a long time to do so. (&lt;i&gt;The food wasn't very good, and the portions were very small.&lt;/i&gt;)&lt;br /&gt;&lt;br /&gt;This bug was suddenly at the top of my list.&lt;br /&gt;&lt;br /&gt;&lt;center&gt;&lt;b&gt;Part 2: Machine Crashers&lt;/b&gt;&lt;/center&gt;&lt;br /&gt;How can machines crash due to user programs? The machine-crashing bug is exceedingly rare in shipping applications; in my experience, there are two primary causes: some system resource gets used up by the program, or the program confuses the window server (the graphics system) and the machine's primary interface locks up.&lt;br /&gt;&lt;br /&gt;As an operating system matures, it's usually harder and harder to confuse the window server (I haven't seen such a bug in OS X in a long time), but resource over-allocation problems remain.&lt;br /&gt;&lt;br /&gt;One of the goals of the operating system's designers is to not allow programs running in user space to ever crash the entire machine. Some would say it's a primary goal, even. But in real life, most operating systems can be brought to their knees (if not fully knocked-out) by user code.&lt;br /&gt;&lt;br /&gt;Consider a simple application that just allocates memory in a tight loop. As the operating system runs this application and starts running out of free physical RAM pages, it starts throwing away the pages used by other applications, in a process that goes kind of like this: oh, you haven't used mail in a few seconds, I'll throw that away for now, and, uh, also this web browser, and, uh, damn, the window server's backing store, and, uh, this essential system font server, and, uh...&lt;br /&gt;&lt;br /&gt;Pretty soon you've effectively locked up the system, just because it's going to take so long to page in any particular resource &lt;i&gt;the windowing system needs to run&lt;/i&gt; that by the time it's loaded off the disk your rogue process has caused some &lt;i&gt;other&lt;/i&gt; vital page to be unloaded.&lt;br /&gt;&lt;br /&gt;Now, the internet is full of flames about how this is completely unacceptable, and OS designers are 100% to blame and yadda yadda yadda. These are, in my opinion, probably from computer science PhD students who believe in a perfect world of provable programming and the Easter Bunny.&lt;br /&gt;&lt;br /&gt;Here in the realm of actually making money, if running your program causes a user's computer to crash, she doesn't care if it's Apple's "fault" &amp;mdash; she's going to post all over the interwebs that &lt;i&gt;your&lt;/i&gt; program sucks, and ask &lt;i&gt;you&lt;/i&gt; for her money back. Now, since you're &lt;i&gt;not&lt;/i&gt; a PhD student, you like money, so this is bad.&lt;br /&gt;&lt;br /&gt;And, honestly, why blame the OS designer? Your app was written incorrectly. It was going to crash or be killed by the OS anyhow. This is a bug you need to fix either way. And the user would be pissed at you even if the OS catches your app and says, "Sorry, this application has gone crazy, we're killing it."&lt;br /&gt;&lt;br /&gt;You can see why OS designers don't spend all their time worrying about such issues. It's not as if a lot of real malware is written to crash machines &amp;mdash; like biological viruses, successful software viruses don't &lt;i&gt;kill&lt;/i&gt; the host, they co-exist with the host and use a minimum of resources while replicating themselves. Viruses that kill the host go extinct very quickly.&lt;br /&gt;&lt;br /&gt;Further, to allow programmers to get the most out of the hardware, OS designers &lt;i&gt;need&lt;/i&gt; to let us live right on the edge. The more blocks and checks they put between us and the hardware, the slower our programs are going to run. Imagine if the Apple said to programmers: "Ok, in order to not let you dominate the hardware, we will only let you use 50% of the CPU at any one time." Now imagine you're a user, running World of Warcraft on your brand-new $4,000 ultra-lux machine, and you're only getting half the framerate you &lt;i&gt;should&lt;/i&gt; be, and &lt;i&gt;would&lt;/i&gt; be under, say, Windows.&lt;br /&gt;&lt;br /&gt;You'd be pissed. And Apple would lose a customer.&lt;br /&gt;&lt;br /&gt;&lt;center&gt;&lt;b&gt;Interlude&lt;/b&gt;&lt;/center&gt;&lt;br /&gt;Look, I don't want to seem mean. I'm sure she's a very nice person in real life, and probably quite smart. And, she's a very handsome woman, I'm not denying that. But, honestly, Jessica Alba can't act. It's just not a skill she has.&lt;br /&gt;&lt;br /&gt;It's OK. Not everyone was meant to be an actress. But you really have to admit your limitations to yourself. I know, for instance, I'll never be a supermodel. I'm OK with that. I'd make a lousy president, too. I accept it.&lt;br /&gt;&lt;br /&gt;It's time to stop, Jessica. You're costing people money. And YOU would be happier doing something you know you're good at, that you could feel good about. There are LOTS of professions for preternaturally beautiful women out there. You could be one of my assistants, for instance.&lt;br /&gt;&lt;br /&gt;&lt;center&gt;&lt;b&gt;Part 3: Diagnosis? Delicious!&lt;/b&gt;&lt;/center&gt;&lt;br /&gt;How do you even &lt;i&gt;begin&lt;/i&gt; to fix a bug where things are going great for a while, then your machine crashes and/or all your files disappear?&lt;br /&gt;&lt;br /&gt;Remember that the very &lt;i&gt;first&lt;/i&gt; thing you do, when looking at any bug, before you even start thinking about it, and &lt;i&gt;long&lt;/i&gt; before you look at your code, is replicate it. You can't debug what you can't replicate, and user reports are usually lacking in some details that your trained eye will catch.&lt;br /&gt;&lt;br /&gt;At this point, I'd already replicated the crash, and I'd seen a valuable clue the user hadn't seen: my files had disappeared. The key clue.&lt;br /&gt;&lt;br /&gt;Next, in a case like this, you'd need to make sure you have a recent backup of your machine, because, &lt;i&gt;damn&lt;/i&gt;. I mean, I've been programming for a long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long time, and I've never seen a bug like that.&lt;br /&gt;&lt;br /&gt;Ever.&lt;br /&gt;&lt;br /&gt;Ok: your machine is crashing, so you suspect that it's running out of some resource. Memory is the most common resource to run out of and the easiest to find and fix, so you run your program under Instruments, with the leak detection tool. You really should have done this before you released your application, but you were totally burnt-out and you'd been in beta for months and didn't seem to have any major issues, so you failed to do everything just right. Now you're paying.&lt;br /&gt;&lt;br /&gt;Your code will run slow as hell under Instruments, but in the end, like that hot gypsy lady at the fair, she will magically tell you, "Hey, you leaked this object exactly here and at exactly this time, so cut it out." It's a small miracle, and the team who wrote this has two to go and they're saints, bitches.&lt;br /&gt;&lt;br /&gt;Sadly, at this point I had also already run Instruments / leaks weeks before, and although it had found a major image leak in 2.0 (whoopsie!) which had kept some users from loading very large libraries, and released it in 2.0.1, this fix didn't help our guy. In fact, my virtual memory usage was holding steady at 1.26 GB as I loaded his collection, which is really not bad considering every process on my system right now has between 0.5 to 1.18 GB of VM right now, under no load.&lt;br /&gt;&lt;br /&gt;Not memory. Hmm. Now you think. Think think think. I think better if I distract myself from the problem and let my hind-brain take over. So I went and played Assassin's Creed, and murdered some fools who had waged war on their fellow man, or made the mistake of calling me peasant, or gotten in my way, or looked funny, or stood around yelling too much about how Salem Ali is a strong man. Honestly, once you start stabbing people in the neck it's hard to stop.&lt;br /&gt;&lt;br /&gt;All the while, I'm swishing ideas around in my mouth to see if they taste right. Graphics memory leak? Often a window server will have its own mini-memory manager: was I overloading it as I converted 40,000 cover images from version one? Maybe, but... Instruments had actually found my leak of graphics memory before, so I kind of trusted that it would have found any others I might have had.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;Stab. Stab stab. Gurgle.&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;Also, my &lt;i&gt;files&lt;/i&gt; had disappeared. That doesn't &lt;i&gt;seem&lt;/i&gt; like a graphics problem.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;Stab. Man, I love jumping from a tall building and landing with my blade in someone's throat. I am so bad-ass.&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;What resources are related to files? Honestly, I don't know a lot about modern file systems; they are kind of a black box to me. When I went to college, we had integers which represented files. And we liked it!&lt;br /&gt;&lt;br /&gt;But Mach had introduced a new kind of I/O, called memory-mapped I/O, that I was using extensively. A possible culprit?&lt;br /&gt;&lt;br /&gt;&lt;center&gt;&lt;b&gt;Part 4: Memories of Memory Mapping&lt;/b&gt;&lt;/center&gt;&lt;br /&gt;Back when NeXTstep 0.8 was first unveiled, one of its key revolutions was it was based on not just plain UNIX, but Mach UNIX, which was a really damn fast and clever re-write of the lowest layers of UNIX, with some extra fun added in, including cheap and fast inter-process communication. Avie Tevanian had been working on Mach as a PhD student at Carnegie Mellon, and Steve Jobs recognized he was a star and hired him straight away. (Microsoft countered by hiring Avie's old advisor to work on NT, which is kind of like Microsoft hiring my mom because I'm a good programmer.)&lt;br /&gt;&lt;br /&gt;One of the great things Mach gave us was a new way to read files, which process was &lt;i&gt;really&lt;/i&gt; slow by default under vanilla UNIX, for reasons I don't fully understand. Mach introduced the metaphor of &lt;i&gt;memory-mapped&lt;/i&gt; files, which used the virtual memory system to map a file on disk onto an address somewhere in VM, without actually loading anything from the disk at first. The cool thing was your program could access any byte in this special VM region, and the correct part of the file would be paged in &lt;i&gt;on-demand&lt;/i&gt;, using the exact same mechanism that the VM system used to extend applications' physical memory by mapping their virtual memory spaces to a large reserved area on the disk.&lt;br /&gt;&lt;br /&gt;This turned out to be very fast, because it had none of the overhead of normal UNIX I/O, which usually involved reading a byte or so at a time using a UNIX function, which involved an expensive system trap and had a lot of overhead (checking to see if enough bytes have been read in from the disk and if not reading in some more) compared to just fetching a memory location. In the latter case, once a page was fetched in you can fetch all the other bytes on that page for free, no system calls at all, just a standard memory access. Big win.&lt;br /&gt;&lt;br /&gt;NeXTstep 1.0 gave us to memory streams, which were awesome, and NeXTStep 2.0 (new capitalization) used memory mapped files by default when you read files using its fancy new NSData class, and all new applications ran a lot faster and life was good.&lt;br /&gt;&lt;br /&gt;Well, mostly good. One problem with memory-mapped I/O is that if the user deletes or moves the file on the disk, the OS can't get to it any more, but the programmer never really knows when this might happen, so she don't know whether to just go ahead and make a copy of the entire file into memory or not. So it's not a good idea to keep memory-mapped files open for a long time.&lt;br /&gt;&lt;br /&gt;Another, more recent, problem is that virtual memory on 32-bit systems is capped at 4GB, which means the largest file you can memory-map is a lot smaller than 4GB (since your app is going to use, like, at least 1GB of VM itself). Nowadays, it's not uncommon for users to have 6GB movie files in their iTunes. Oops.&lt;br /&gt;&lt;br /&gt;So, for a while now, Apple has been moving gently away from memory-mapping I/O. For example, you have to set a flag (CSResourcesFileMapped) in your applications' Info.plist if you want to memory-map the resource files that the AppKit and Foundation frameworks load for you; otherwise they are read using different (unknown) techniques, which may or may not be slower (see below).&lt;br /&gt;&lt;br /&gt;Fortuitously, however, I'd just been part of a discussion on a developer mailing list about optimized file system access, and learned an important fact, which gave me the solution.&lt;br /&gt;&lt;br /&gt;&lt;center&gt;&lt;b&gt;Part 5: Everything New is Old Again&lt;/b&gt;&lt;/center&gt;&lt;br /&gt;I have exalted memory-mapped I/O for exactly twenty years now, but just a week or so ago an Apple engineer told me that the latest tests show that Apple's new-fangled "uncached" access method had at least as good of performance as memory-mapped I/O, with neither of the two big drawbacks listed above. It was The New Way.&lt;br /&gt;&lt;br /&gt;Here's a bit from the &lt;a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSData_Class/Reference/Reference.html" target="other"&gt;NSData page&lt;/a&gt;:&lt;dl class="termdef"&gt;&lt;a name="//apple_ref/c/econst/NSMappedRead" title="NSMappedRead"&gt;&lt;/a&gt;&lt;a name="//apple_ref/doc/c_ref/NSMappedRead" title="NSMappedRead"&gt;&lt;/a&gt;&lt;a name="//apple_ref/doc/uid/20000172-DontLinkElementID_52"&gt;&lt;/a&gt;&lt;dt&gt;&lt;code&gt;NSMappedRead&lt;/code&gt;&lt;/dt&gt;&lt;dd&gt;A hint indicating the file should be mapped into virtual memory, if possible.&lt;br&gt;Available in Mac OS X v10.4 and later.&lt;br&gt;Declared in &lt;code&gt;NSData.h&lt;/code&gt;&lt;br&gt;&lt;/dd&gt;&lt;a name="//apple_ref/c/econst/NSUncachedRead" title="NSUncachedRead"&gt;&lt;/a&gt;&lt;a name="//apple_ref/doc/c_ref/NSUncachedRead" title="NSUncachedRead"&gt;&lt;/a&gt;&lt;a name="//apple_ref/doc/uid/20000172-DontLinkElementID_53"&gt;&lt;/a&gt;&lt;dt&gt;&lt;code&gt;NSUncachedRead&lt;/code&gt;&lt;/dt&gt;&lt;dd&gt;A hint indicating the file should not be stored in the file-system caches.&lt;br&gt;For data being read once and discarded, this option can improve performance.&lt;br&gt;Available in Mac OS X v10.4 and later.&lt;br&gt;Declared in &lt;code&gt;NSData.h&lt;/code&gt;&lt;br&gt;&lt;/dd&gt;&lt;/dl&gt;Sadly, there's not a lot of guidance there when you should or shouldn't use either option. Also, there's no hint if the options are mutually exclusive. Why would you use NSMappedRead, and why not? Who knows?&lt;br /&gt;&lt;br /&gt;But now I'd heard the Word, and It Was Good. I resolved then and there to look over my code, and see when I did and didn't need NSMappedRead. But I didn't want to just naively trust that NSUncachedRead was &lt;i&gt;always&lt;/i&gt; better (APIs so rarely work like that), so I'd need to do timing tests. So, in fact, I was in the middle of these timing tests (on iTunes importing) when I got this new bug report, so I had some experience with memory-mapped vs. cached access.&lt;br /&gt;&lt;br /&gt;&lt;center&gt;&lt;b&gt;Part 6: I Love It When a Plan Comes Together&lt;/b&gt;&lt;/center&gt;&lt;br /&gt;Ok, I'm reading in 40,000 image files as part of the conversion, and storing them in my database. At some point the filesystem goes on vacation and says, to hell with you, I'm not reading any more damn files, even if they are just directories. Hrmmm. Hrmmmmmmmmmm.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;"Oh, please, spare my li-- gurgle!"&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;Maybe the problem was there was some huge bug in Apple's Mach, where if you open too many files in a short period of time, the filesystem tried to, like, cache the results, and the cache blew up, and as a result the filesystem incorrectly just would fail to open any more files, instead of flushing the cache. This made a &lt;i&gt;little&lt;/i&gt; sense, since the problem had gone away spontaneously after a minute or so, like a cache problem would when the cache auto-flushed.&lt;br /&gt;&lt;br /&gt;One thing I knew, KNEW wasn't the problem was that I had too many file open simultaneously. Because the way my code worked, I'd open the files using NSData's dataWithContentsOfMappedFile:, do a little massaging on the image inside, and then the files would auto-close themselves when the NSData was autoreleased, which I would correctly do every 100 files I read in.&lt;br /&gt;&lt;br /&gt;I &lt;i&gt;knew&lt;/i&gt; this. Knew! I've also been around long enough to know that whenever I &lt;i&gt;know&lt;/i&gt; the operating system must be bugged, since &lt;i&gt;my&lt;/i&gt; code is correct, I should take a damn close look at my code. The old adage (not mine) is that 99% of the time operating system bugs are actually bugs in your program, and the other 1% of the time they are still bugs in your program, so look harder, dammit.&lt;br /&gt;&lt;br /&gt;So I traced through the steps. First I read the image file into the NSData. Right. It's auto-released, so I'm not leaking it. Then, I set the CoreData object's data field to point to the same NSData. Still no leak. Then, the NSData is autoreleased, correctly, and the system resources should be freed up.&lt;br /&gt;&lt;br /&gt;Wait a minute. Wait. It's autoreleased, but that doesn't mean it's &lt;i&gt;deallocated&lt;/i&gt;. It just means the reference count is decremented. But someone else might still have a pointer to it... in fact, I had &lt;i&gt;just&lt;/i&gt; assigned the same NSData to the CoreData object. Knowing it was an immutable data, the CoreData object didn't make a &lt;i&gt;copy&lt;/i&gt; of my data, it just retained the &lt;i&gt;exact same&lt;/i&gt; NSData object. The one that had an open memory-map of the image file!&lt;br /&gt;&lt;br /&gt;CoreData's objects persist in memory until you flush them explicitly, which means anything they are holding also persists. So, I was creating 40,000 memory-mapped files and not freeing any of them. That, uh... seemed fishy. Like, five-course-sushi-dinner fishy.&lt;br /&gt;&lt;br /&gt;Now, I could flush these cover objects explicitly using CoreData; I'd have to read in a batch of old objects, convert them, save everything, then flush the covers, then read in another batch. In fact, I was already using batches (following &lt;a href="http://developer.apple.com/documentation/Cocoa/Conceptual/CoreData/CoreData.pdf" target="other"&gt;CoreData's best practice guidelines&lt;/a&gt;, see "Importing in Batches"), but flushing would add a layer of complexity. I'd have to keep track of the objects I created separately, and loop through and flush them.&lt;br /&gt;&lt;br /&gt;Sure, that's easy code to write. But I'm making a 2.0.2 release, and in x.x.y releases, I want to change as little code as possible to fix the bug, because my beta testers weren't going to be excited about testing a release that adds no features. I will test it myself, of course, and I'd ask anyone who reported the bug to test it, but doing something as heavy-duty as suddenly flushing a bunch of objects &lt;i&gt;could&lt;/i&gt; have side effects, and I wouldn't have enough people to test for those. Yes, in theory flushing &lt;i&gt;shouldn't&lt;/i&gt; have side-effects, but in that same world, no code should ever have bugs and communism is a really nifty idea.&lt;br /&gt;&lt;br /&gt;But, I'd been told that NSUncachedRead was as good as NSMappedRead, without as many drawbacks. Would it also not tie up the same system resources? Certainly the word "uncached" seemed promising. I'd try it.&lt;br /&gt;&lt;br /&gt;I kind of exaggerated on the "one word" in my preamble &amp;mdash; it &lt;i&gt;would&lt;/i&gt; have been a one word change, but I'd used the older interface on NSData:&lt;br /&gt;&lt;br /&gt;&lt;table class="codeTable"&gt;&lt;tr&gt;&lt;td  class="codeCell"&gt;   NSData *imageData = [NSData dataWithContentsOfMappedFile:fullImagePath];&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;br /&gt;I replaced it with this line (which would say NSMappedRead instead of NSUncachedRead to be equivalent to the above):&lt;br /&gt;&lt;br /&gt;&lt;table class="codeTable"&gt;&lt;tr&gt;&lt;td  class="codeCell"&gt;    NSData *imageData = [NSData dataWithContentsOfFile:fullImagePath&lt;br /&gt;        options:NSUncachedRead error:&amp;error];&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;br /&gt;Thus removing the memory-mapping.&lt;br /&gt;&lt;br /&gt;Importing the large collection worked perfectly. The problem was solved, and now I could upgrade truly HUGE collections.&lt;br /&gt;&lt;br /&gt;&lt;center&gt;&lt;b&gt;Part 7: Where Did We Go Wrong?&lt;/b&gt;&lt;/center&gt;&lt;br /&gt;We had tested Delicious Library 2 with importing an enormous sample file of 10,000 Delicious Library 1 items. However, it took so long to build up such a library in version 1 that we ended up building the sample library using a custom program, and as a result that test file didn't have any images in it. But beta testers were enrolled specifically because they had collections of thousands of items, and we had the largest closed beta of any program I've worked on. We also tested with our friends' Delicious Library 1 files, of about 3,300 items.&lt;br /&gt;&lt;br /&gt;But this customer had over 4,400 items in Delicious Library 1. That, it turned out, was a few over a limit we had no idea existed. Damn it.&lt;br /&gt;&lt;br /&gt;It's a bug we should have caught. We should have spent the time to get the images in the 10,000 item file. I messed up.&lt;br /&gt;&lt;br /&gt;Software is written by humans. Humans get tired. Humans become discouraged. They aren't perfect beings. As developers, we want to pretend this isn't so, that our software springs from our head whole and immaculate like the goddess Athena. Customers don't want to hear us admit that we fail.&lt;br /&gt;&lt;br /&gt;The measure of a man cannot be whether he ever makes mistakes, because he &lt;i&gt;will&lt;/i&gt; make mistakes. It's what he does in response to his mistakes. The same is true of companies.&lt;br /&gt;&lt;br /&gt;We have to apologize, we have to fix the problem, and we have to learn from our mistakes.&lt;br /&gt;&lt;br /&gt;Unfortunately, Delicious Monster has so much mail from releasing version 2, we're failing at responding to customers right now. We are a tiny company, and had one support person, and although we're bringing a second up to speed, it takes time to teach someone how to solve our users' problems. Our backlog of mail messages is finally going down &amp;mdash; as of today, we're at 1,300, down from 3,000 a week or so after our release. We still get several hundred messages a day, though, and it takes time just to sort through and see which ones are bugs and which ones are customers needing help and which ones are people just saying, "Hey, that's cool, you know what else would be cool?"&lt;br /&gt;&lt;br /&gt;When this customer finally got through to me, I apologized to him for the bug and the crummy response time, and elevated his bug to the top. I let him know what I was doing to fix it, and got the fix done in a couple days. I also gave him a free license to Delicious Library 2, since he'd been unable to buy it yet.&lt;br /&gt;&lt;br /&gt;I had already known that when I launched 2.0, I couldn't immediately go on vacation; that I'd have to jump in to the 2.0.1 release for any urgent bugs that customers found. What I didn't realize was how much support e-mail would spike. That's the lesson I learned: I need to have extra people ready to do support when I do a major release. I'm not sure how to do this and not be paying people to twiddle their thumbs the rest of the time, but that's something I'll have to figure out. And I apologize to my customers for this screw-up.&lt;br /&gt;&lt;br /&gt;The beta for 2.0.2 is out now, at &lt;a href="http://delicious-monster.com/downloads/Delicious%20Library%202%20Beta/DeliciousLibrary2.zip"&gt;http://delicious-monster.com/downloads/Delicious%20Library%202%20Beta/DeliciousLibrary2.zip&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;It'll load a ton of items.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/11049281-239909600890133322?l=wilshipley.com%2Fblog' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/239909600890133322/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=11049281&amp;postID=239909600890133322' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/239909600890133322'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/239909600890133322'/><link rel='alternate' type='text/html' href='http://wilshipley.com/blog/2008/07/pimp-my-code-part-15-greatest-bug-of.html' title='Pimp My Code, Part 15: The Greatest Bug of All'/><author><name>Wil Shipley</name><uri>http://www.blogger.com/profile/04429428424208378747</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='07967666282086327688'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-11049281.post-4730040031687472185</id><published>2008-06-21T02:23:00.000-07:00</published><updated>2008-06-21T02:40:43.478-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='stories'/><category scheme='http://www.blogger.com/atom/ns#' term='business'/><title type='text'>Delicious Library 2 has Shipped!</title><content type='html'>Ok, well, actually &lt;a href="http://delicious-monster.com/" target="other"&gt;Delicious Library 2&lt;/a&gt; shipped like four weeks ago. Sorry I didn't tell you. It's not because I don't care. Honestly, I tried to tell the press, but I think they're pretty sick of me, so there wasn't much of a splash on the news sites.&lt;br /&gt;&lt;br /&gt;Sales have been great, though, so I can't complain.&lt;br /&gt;&lt;br /&gt;But, hey, enough about me... what's up with your life? (Use blank space provided.)&lt;br /&gt; &lt;br /&gt;_______ ______ _______ _____ _____ _____ ____&lt;br /&gt;&lt;br /&gt;Really? Wow. Well, you know, stuff changes. I think it'll work out.&lt;br /&gt;&lt;br /&gt;Oh, also, I launched a new company down at WWDC last week, called &lt;a href="http://golden-braeburn.com/" target="other"&gt;Golden % Braeburn&lt;/a&gt;. I'm going to license out the store I wrote to sell Delicious Library, since it was a huge pain in the tush to write, and I actually would have licensed a store from someone else if there'd been a decent option back when we launched Delicious Monster.&lt;br /&gt;&lt;br /&gt;I know, it seems kind of pretentious for one guy to have two companies. Even *I* blush a little at it. But, it kind of makes sense &amp;mdash; Golden % Braeburn's only going to have customers in the dozens, and they'll all be Mac developers. I didn't want to confuse my Delicious Monster customers by saying, "Oh, hey, now that you've bought Delicious Library, would you maybe be interested in buying a store to sell software on the internet?" That doesn't pass the "mom" test. (Incidentally, as my mom gets battier I'm finding it harder and harder to write software that passes the "mom" test. I'll have to go back to using Matas' mom as the eponymous mom from the test.)&lt;br /&gt;&lt;br /&gt;You can see how it kind of de-focuses my message, yes?&lt;br /&gt;&lt;br /&gt;---&lt;br /&gt;&lt;br /&gt;A lot of people have been asking about Pimp My Code... No, it's not dead, it's just that those entries take approximately a day to write, and when I was in the final months (and months and months) of Delicious Library 2 I really felt like I owed it more to my customers to actually write my dang software than to publish my vanity blog. (I know, I wrote about my cat and girls and stuff &amp;mdash; those entries take like half an hour. I don't have to fact-check them or anything.)&lt;br /&gt;&lt;br /&gt;So, we'll have some code pimping here soon... coming up first is the on-the-fly localization code that's part of what I'll be sharing with all Golden % Braeburn customers (one of the advantages of licensing the store is you also get all my helper code), and also the system I'm trying to get Apple to switch to. You can evaluate for yourself whether it's better than AppleGlot or FoobleBlot or whatever you are using.&lt;br /&gt;&lt;br /&gt;---&lt;br /&gt;&lt;br /&gt;On a personal note, recently my shrink said to me, "Hey, Wil, why don't you drop the pimp act? Nobody actually looks at show-offs and thinks, 'Oooh, I like him.' In fact, everyone resents them."&lt;br /&gt;&lt;br /&gt;This made a lot of sense, so I'm officially renouncing my phony pimpitude. Honestly, I'm just a geek who stays up late and plays GTA and makes clumsy passes at pretty girls and tries to write software. That's me.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/11049281-4730040031687472185?l=wilshipley.com%2Fblog' alt='' /&gt;&lt;/div&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/4730040031687472185'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/4730040031687472185'/><link rel='alternate' type='text/html' href='http://wilshipley.com/blog/2008/06/delicious-library-2-has-shipped.html' title='Delicious Library 2 has Shipped!'/><author><name>Wil Shipley</name><uri>http://www.blogger.com/profile/04429428424208378747</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='07967666282086327688'/></author></entry><entry><id>tag:blogger.com,1999:blog-11049281.post-6133049425738026354</id><published>2008-04-12T03:52:00.000-07:00</published><updated>2008-04-12T03:54:30.195-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='stories'/><title type='text'>It's just a story.</title><content type='html'>In 1987 I was a senior in high school and my mother was in the hospital with leukemia; a long, very painful experimental treatment would either cure her (but leave her changed for life) or she would die.&lt;br /&gt;&lt;br /&gt;With the profits I'd made from my summer job, I had bought a $400 Technics portable CD player, one of the very first ones ever made. It was solid metal and heavy as hell, and the rechargeable battery pack was as big as the CD player itself and weighed twice again as much. It still plays perfectly to this day.&lt;br /&gt;&lt;br /&gt;I was left with her lame car and a giant house to myself and not much supervision. It would have been many a teen's dream, but not mine, since I was truly alone; after I moved back to the States for the eleventh and twelfth grades I never once had a friend over after school, or went over to a friend's house, or went to a party, or a dance; I worked in my little computer lab from when I got home to when I went to sleep. I graduated with honors but didn't show up. I was programming at the time.&lt;br /&gt;&lt;br /&gt;Some nights I would take my mom's (new-model) Chevy Nova out and just drive around the waterfront, listening to my little CD player. There were two discs in particular for when I felt most alone: Steve Winwood's "Back in the High Life" and Peter Gabriel's "So". Both artists were consummate musicians, at the height of their craft -- neither would ever make another album as successful. Both created incredibly rich soundscapes, and both talked about loss, and longing.&lt;br /&gt;&lt;br /&gt;I don't share either album with people very often, these days, because I've discovered I am incredibly upset if my guests are anything below astounded by them. I require rapt attention, possibly sighs, if you hear the Blessed Two. I feel as if I am physically cutting away my skin and pulling it aside with tongs to show my viscera, the actual core of my being, and if my listeners are all, "Can we put on some GOOD music after this?" I just want to smite them.&lt;br /&gt;&lt;br /&gt;To this day if I hear "Don't Give Up" I will cry. I may not bawl, but you can see little tears in my eyes. I can see the park on Lake Washington I would drive to. I can feel the slight cold of the wind through my not-at-all-fashionable windbreaker. I can see the giant CD player on its huge strap around my neck. And I feel the hurting, of wanting to not be alone. Of waiting it to be over.&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;The happiest and saddest part, I think, of liking someone of the opposite sex... really liking, as in, really admiring the person, thinking that she is, in fact, a really good person, a decent person, a person whose morals and smarts and sense of humor and accomplishments you actually think are amazing -- not just, like, "Damn, she got pretty tummy," which latter sentiment I have also fallen prey to -- the happiest and saddest part is that you become someone different when you feel this way.&lt;br /&gt;&lt;br /&gt;I don't want to, and won't, use the stupid cliché from the stupid movie. But it's true. You make yourself into a better person, not to trick them into liking you, but because _they deserve it_, and _you want to be a person that deserves them_. The difference is everything.&lt;br /&gt;&lt;br /&gt;It's the saddest part because when you lose the hope, the dream, the focus -- well, you want to hold on to that you, that better you, that you that you liked so much, the you that you were with her. It's inside you. Were you faking? No. You have it. Just continue being it. Just don't stop. Be more patient with people you see. Smile at them. Let tiny things go, ignore any little slight, be generous with praise. Be that person. You can still do it. Hold on to him.&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;An interesting, if bizarre, factoid about me is that I cry if I see kids under the age of 10. I also cry if I see child's toy aimed at under-10-year-olds. And, finally, I can remember only two or three scenes from my life from before I was 10: My dad reading "One Fish, Two Fish" to me to teach me what words looked like. (Read to your kids! It's more important than you think.) My parents in bed on a lazy Sunday and the kids coming in and hassling them. Running to get one of the Big Wheels in recess in kindergarden, because there were only a couple and if you didn't get one recess was lame. The other kids wanting to build a boat out of toy cardboard bricks, and me, the quiet kid who never spoke up, finally saying something: I have a plan. I can build a boat. Show us, show us, and I did, and for that one day, for that afternoon, I was the hero.&lt;br /&gt;&lt;br /&gt;The rest of my childhood is gone. I don't know where that person is. He's very sad, though.&lt;br /&gt;&lt;br /&gt;Yes, I'm in therapy, thanks for checking.&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;I listen to Steve Winwood again, on my expensive studio monitor speakers, the likes of which I couldn't dream of when I was 18. His album still sounds great to me, after all these years.There's a part of me that's conscious of all the time that's passed: that the rich, full sound I loved is now considered cheesy and overproduced, that nobody has heard of Steve Winwood in twenty years, Peter Gabriel is just another dude at TED, and that schmaltzy emotions are for angsty teenagers with zits and five-year-long erections.&lt;br /&gt;&lt;br /&gt;But there is a little kid who has felt alone all his life, and he wants it to end. When will it be over? Will I die first? Why are you so old? What have we done with our life? Why are we alone? How did you manage to fail in this, the one thing that mattered.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/11049281-6133049425738026354?l=wilshipley.com%2Fblog' alt='' /&gt;&lt;/div&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/6133049425738026354'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/6133049425738026354'/><link rel='alternate' type='text/html' href='http://wilshipley.com/blog/2008/04/its-just-story.html' title='It&apos;s just a story.'/><author><name>Wil Shipley</name><uri>http://www.blogger.com/profile/04429428424208378747</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='07967666282086327688'/></author></entry><entry><id>tag:blogger.com,1999:blog-11049281.post-2431425723751061940</id><published>2008-03-24T00:51:00.000-07:00</published><updated>2008-03-24T00:59:10.366-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='stories'/><title type='text'>There is a moment...</title><content type='html'>There is a moment when you are touching a woman, innocently, you say, innocently, she says, but you are massaging her back, stroking her hair, running your fingers lightly over her face.&lt;br /&gt;&lt;br /&gt;There is a moment when you think to yourself, "I want to kiss her, I should kiss, I am going to kiss her."&lt;br /&gt;&lt;br /&gt;And you know that if you are wrong, you will feel stupid. And she will leave. And you will apologize. And everything will be spoiled.&lt;br /&gt;&lt;br /&gt;But the kiss is still hanging there. Evolution is stronger than you. It doesn't care if you feel stupid. Kiss the neck, it says. She smells wonderful, and you should kiss. Necks were created to be kissed. They crave it. They are empty without it.&lt;br /&gt;&lt;br /&gt;There is only one thing worse than her rejecting you, and that is if you do NOT kiss her neck, in this moment, right now. You have lived your whole life for this. You dream of this moment every day.&lt;br /&gt;&lt;br /&gt;There is a moment when you kiss her, lightly, on the neck, and instead of leaving, instead of being outraged, she breathes. You hear her breath, you feel her breath. And you have lived your whole life for that moment.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/11049281-2431425723751061940?l=wilshipley.com%2Fblog' alt='' /&gt;&lt;/div&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/2431425723751061940'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/2431425723751061940'/><link rel='alternate' type='text/html' href='http://wilshipley.com/blog/2008/03/there-is-moment.html' title='There is a moment...'/><author><name>Wil Shipley</name><uri>http://www.blogger.com/profile/04429428424208378747</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='07967666282086327688'/></author></entry><entry><id>tag:blogger.com,1999:blog-11049281.post-6660912869301801031</id><published>2008-02-29T16:11:00.000-08:00</published><updated>2008-02-29T16:14:29.199-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='stories'/><title type='text'>TED2008 - Part 1: Gossip</title><content type='html'>I am back, after a fashion. The night before TED, my new MacBook Air was stolen out of the lobby of the Portola Plaza hotel, leaving me in the dark for days, feeling incredibly violated.&lt;br /&gt;&lt;br /&gt;My old, now kind of stinky-seeming MacBook Pro has been shipped to me, so at least I can connect to the sweet mother intertron, whose warm nectar I crave daily. Also, I can now track down the frakker who took my Air, so you better hope you wipe that disk good &lt;i&gt;and&lt;/i&gt; don't ever connect to the net again. Or sell it to anyone who, like, has heard of me.&lt;br /&gt;&lt;br /&gt;[My MacBook Air serial number was W880311W12G and the "MAC" or Airport ID was "001EC2B605B9". If you see this machine it is stolen and you should call the Monterey police at 831.646.3830 and reference case number 08-1077. Intertron powers activate!]&lt;br /&gt;&lt;br /&gt;-- &lt;br /&gt;&lt;br /&gt;I should start with a story which sounds like bragging, but you will quickly discover, is actually me fulfilling my duties as a gentleman.&lt;br /&gt;&lt;br /&gt;Last night at Crown and Pig and Whistle and Anchor bar I was talking with an attractive you woman TEDster, who, after I convinced her I was &lt;b&gt;not&lt;/b&gt; gay (there had been a HI-larious &lt;i&gt;Three's Company&lt;/i&gt; style mixup that's not actually particularly funny so I won't recount it here) she proceeded to lean over and whisper in my ear for five minutes about who she actually WAS attracted to (said list not including me, if that needs to be made explicit).&lt;br /&gt;&lt;br /&gt;After a few moments of this, I pointed out the irony that everyone else at the table, including my new rival Jonathan Hodgman, thought that she was leaning over and whispering to me because she was into me, not because I had become her new eunuch confidant. (Speaking of Hodgman, who KNEW he was such a ladies' man? He was surrounded by pretty girls the whole time. Of course, being the perfect family man, he acted the gracious gentleman -- you thought I was going to get him in trouble with his wife, didn't you? That's not how I roll. I've never even posted my really juicy ultimate cock-block story about LP from a few years ago, and he wasn't married then.)&lt;br /&gt;&lt;br /&gt;So, being informed that it looked as though we were flirting, and her being a game sort with a wicked streak, she was all, "oooooh!" and turned fully towards me and put her hand on my shoulder and leaned in close to my ear, so her lips just brushed its tiny hairs with every word as she spoke, sending a little involuntary tingle up my spine with every warm, wet breath as she seductively whispered, "So, should I pretend I like you, like this?" &lt;br /&gt;&lt;br /&gt;Then she bit my ear.&lt;br /&gt;&lt;br /&gt;No, no, sorry, I'm lying: the hairs on my ears aren't "tiny" any more. They are stark white and surprisingly sturdy and grow to be, like, four feet long. I'm like fucking Yoda. I've gotten to the point where I don't even bother clipping them; I see my body as some kind of bizarre science experiment as it deteriorates and I'm actually curious to see how long any given hair in any given spot will get. A week ago I had an eyebrow hair that was, no shit, two inches long -- Mike tried to pluck it for me and I got protective of it, like it was my tomagotchi. Sometimes I have races between the hairs on my left ear and the ones on the right.&lt;br /&gt;&lt;br /&gt;Anyhow, the point is, for anyone in the bar that night, I shall protect the young lady's honor by giving up the game -- she was, in fact, just making a scandal for scandal's sake; trying to help my pimp cred... an act of charity from a kind stranger. I'm not not not saying I didn't not not enjoy it -- any bone looks like top sirloin to a hobo, and it's been a too long since I've been thrown a bone.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/11049281-6660912869301801031?l=wilshipley.com%2Fblog' alt='' /&gt;&lt;/div&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/6660912869301801031'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/6660912869301801031'/><link rel='alternate' type='text/html' href='http://wilshipley.com/blog/2008/02/ted2008-part-1-gossip.html' title='TED2008 - Part 1: Gossip'/><author><name>Wil Shipley</name><uri>http://www.blogger.com/profile/04429428424208378747</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='07967666282086327688'/></author></entry><entry><id>tag:blogger.com,1999:blog-11049281.post-3542284008220396844</id><published>2008-02-17T16:36:00.000-08:00</published><updated>2008-02-17T18:04:23.445-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='mac community'/><title type='text'>MacBook Air: Rambling First Impressions (PG!)</title><content type='html'>Lots of people have asked for my impressions, so I thought I'd post a more sober (literally) look at life with my little MacBook Air. With no cussing!&lt;br /&gt;&lt;br /&gt;• It feels really nice, like a pebble. A large, smooth pebble, from a stream. This shape speaks to me, like the MOTOPEBL did, except that was a crappy phone and not a really nice computer.&lt;br /&gt;&lt;br /&gt;• It is super-solid. It feels way more structurally solid than any laptop I've ever owned. I don't know if this like a synesthetic illusion because it is so beautiful, or because it has curved surfaces (== less flex!), or because it's just so darn light that there's not a lot of mass to flex.&lt;br /&gt;&lt;br /&gt;• The prominent feet and rigity make the machine seem wobbly on anything but a 100% level surface. The antique wood tables at Zoka are not that perfect, so every time I type the machine rocks like a shopping cart at K-mart. Mushier feet, maybe? I dunno, you guys are the geniuses, you figure it out. But, seriously, wobbling things make me nuts. I'm going to start stuffing napkins under the corners of the machine, and that's not good advertising. [UPDATE: John Siracusa provided a great suggestion for this: the MacBook Air should have only three feet; three points always form a plane.]&lt;br /&gt;&lt;br /&gt;• I love how the port door on the right opens and closes; it's a very solid-feeling mechanism, and very natural. Also, I feel like I'm in Star Trek (the new one).&lt;br /&gt;&lt;br /&gt;• I don't run on battery much, but I've noticed it seems to take a billion years to charge it if it gets discharged, at least the first couple times. Odd.&lt;br /&gt;&lt;br /&gt;• I got the 64GB SSD. It seems pretty awesome, but I can't fit my (legal) iTunes collection on it, even without movies, after I put my iPhoto collection on it and my source code and just a couple apps (Acorn, Twitterific, Zuma, iWork so far, MarsEdit coming).&lt;br /&gt;&lt;br /&gt;• I'm moving over my old stuff as I need it. Copying stuff over AirPort is super-slow, but the ethernet adaptor is pretty decent. I tried to copy World of Warcraft from a friend's PowerBook (I have a legal copy, don't worry) and it was scheduled to take five hours, since she doesn't have 802.11n, even. Using the ethernet adaptor it was, like, five minutes. No surprises here, but the take-away message is, ethernet adaptor is a good idea.&lt;br /&gt;&lt;br /&gt;• I don't try to access CDs or DVDs from my machine -- my previous machine didn't even have a working drive -- so I don't really care that it doesn't have one built-in. The external one is a thing of beauty and I almost want to buy it just &lt;i&gt;because&lt;/i&gt;, but it doesn't work with other machines so that kind of stinks.&lt;br /&gt;&lt;br /&gt;• The screen is so very, very bright compared to the (1st-gen) MacBook Pro. Games look much better. It's not something you realize you want until you get it -- you think increases in resolution or color depth are cool, but when you get a brightness upgrade this dramatic you realize AH! THIS is what I really wanted! Who needs &lt;i&gt;more&lt;/i&gt; pixels when each of my pixels now shines so very, very brightly? ("I've seen things, you people wouldn't believe...")&lt;br /&gt;&lt;br /&gt;• I think the machine's smallness is tearing up my neck. I'm sitting 8-10 hours a day working on this thing, and I end up looking DOWN at it more than my 15" MacBook Pro. I've had neck cramps since I got it, but I'm still adjusting, and I'm also in crunch mode with Delicious Library 2.&lt;br /&gt;&lt;br /&gt;• It compiled Delicious Library 2 from scratch in 1'59". The 2.33GHz MacBookPro takes 2'04". SSD's LOVE compilations.&lt;br /&gt;&lt;br /&gt;• SSD's love context switching, as well. Having an SSD is a lot like having 64GB of RAM in your machine. Sure, I'm going to lose in a Photoshop filter race with your machine, but I'm going to crush you switching between the 15 applications I have open right now. Again, it's not a surprise to say that if video editing or cutting-edge video games is your primary purpose, you'll probably find the MacBook Pro faster. But if you're writing software or just snurfing the web and running lots of apps, this machine is faster.&lt;br /&gt;&lt;br /&gt;• Bizarrely, it still has a sudden motion sensor in it. Think about that for a minute.&lt;br /&gt;&lt;br /&gt;• More bizarrely, if I drop the Air a foot (onto a soft, fluffy pillow on my bed -- I'm not an idiot) the sudden motion sensor will still shut down the SSD (tell it to park its heads?) and stop processing for a second. I think that's pretty funny. Hey, hardware guys: "SSD stands for SOLID-STATE DISK."&lt;br /&gt;&lt;br /&gt;• I admit there could still be problems I don't know about with dropping SSDs, and I'm just being snide. I'm sorry, hardware guys. Still friends? Buy you a drink? Hug it out?&lt;br /&gt;&lt;br /&gt;• I like using the "pinch" gesture. That's the only one I've really used. So far, it works great in Finder (icon mode) and iPhoto and Safari (just feels bizarre there, honestly) and two places Delicious Library 2 (shhh!). It's the right solution.&lt;br /&gt;&lt;br /&gt;• The "swipe" gesture should have been mapped to "start scrolling and then after I stop the swipe keep scrolling slower and slower until you stop naturally or I stop you" like scrolling works on the iPhone. The Air team didn't ask me, but they should have. This would have been trivial to add to Cocoa (we added it experimentally once to DL2, may put it back). Sure I could file a RADAR bug on this, but isn't it more fun to complain on my blog like a prima donna? (Yes. Yes it is.)&lt;br /&gt;&lt;br /&gt;• Jonathan Ive should design a laptop bag as beautiful as the Air, that just can contain the machine, a power cord, and a Wireless Mighty Mouse. I'd be in heaven. Nobody seems to have addressed the "I want a small, slim bag that can still hold a power cord without having a giant wart in the side" market. Like, duh, bag designers, STOW THE POWER CORD ABOVE OR BELOW THE LAPTOP, not STICKING OUT THE SIDE WHERE IT CREATES A TENT AND LOOKS UGLY AND BANGS MY KNEE.&lt;br /&gt;&lt;br /&gt;• The Air runs World of Warcraft pretty damn well. Sure, I don't have, uh, specular water reflective anti-aliased spectroscopic quadrophonic roto-tilling turned on. But, you know, I can, like, heal things and run around and pick liferoot and run around some more. (PHEAR MY HEALING, EVIL-DOERS OF AZEROTH!)&lt;br /&gt;&lt;br /&gt;• The Air's main performance limitation is heat, and mainly from the GPU. When it starts doing graphical things, it gets hot. When it gets hot, it starts venting out the bottom-back. If there's not enough clear vents (like, if you are in bed, and it's resting in your lap so the bottom vents are perfectly pressed into the fluffy down comforter) then it underclocks the GPU and you go into slide-show mode. This will happen in Zuma if you try hard enough, or if you're watching Hulu.com, even, but it's pretty easy to get it in World of Warcraft.  Throwing off your comforter and getting nakeder with your Air is the only solution at this point, and also, it feels... so deliciously wrong.&lt;br /&gt;&lt;br /&gt;• Note to hardware guys: don't put vents there, bokay? Laptops are for bed. Don't put vents right where the laptop touches my leg. (Aw, come on back, hardware guys! I still love you! Look, sometimes I just get a little angry, and when I've been drinking, well, you know my temper...)&lt;br /&gt;&lt;br /&gt;• On the other hand, if this baby is plugged in and sitting on a flat surface, I can play Teh WoWz all day and it's great. (Not great for shipping DL2, so I don't do it, but I &lt;i&gt;could&lt;/i&gt;. It's nice to know it's there, like a beautiful ex who still wants to have sex with you.)&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;This isn't a machine for everyone, nor should it be. Just as there should be &lt;a href="http://www.ted.com/index.php/talks/view/id/20" target="other"&gt;three types of spaghetti sauce&lt;/a&gt;, you and I should not HAVE to agree on what we want in a machine. The machine should, instead, be designed to agree with us.&lt;br /&gt;&lt;br /&gt;I admit my last post was a bit over-the-top; my point was supposed to be: "Look, this machine may not be for you, personally, but please acknowledge that there are people for whom it is perfect." For instance, Gabe told me he wants a new MacBook Pro, and I didn't try to push the Air on him (...much). He's an artist and a gamer. He wants pixels, and lots of them, and FAST. The MacBook Pro is going to run his Windows games faster than pretty much every laptop.&lt;br /&gt;&lt;br /&gt;I &lt;i&gt;will&lt;/i&gt; try to steer him towards the biggest MacBook Pro that has the LED backlight, because it's just SO DARN PRETTY. And if anyone offered a 128GB SSD, I'd be recommending that to all my friends who have cash to burn. Because it's the future, baby, and it's beautiful.&lt;br /&gt;&lt;br /&gt;-W&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/11049281-3542284008220396844?l=wilshipley.com%2Fblog' alt='' /&gt;&lt;/div&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/3542284008220396844'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/3542284008220396844'/><link rel='alternate' type='text/html' href='http://wilshipley.com/blog/2008/02/macbook-air-rambling-first-impressions.html' title='MacBook Air: Rambling First Impressions (PG!)'/><author><name>Wil Shipley</name><uri>http://www.blogger.com/profile/04429428424208378747</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='07967666282086327688'/></author></entry><entry><id>tag:blogger.com,1999:blog-11049281.post-1326760229234214113</id><published>2008-02-09T17:18:00.000-08:00</published><updated>2008-02-09T17:22:18.472-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='mac community'/><category scheme='http://www.blogger.com/atom/ns#' term='interface design'/><category scheme='http://www.blogger.com/atom/ns#' term='code'/><category scheme='http://www.blogger.com/atom/ns#' term='advice'/><category scheme='http://www.blogger.com/atom/ns#' term='business'/><title type='text'>My C4[1] Talk...</title><content type='html'>Mr. Rentczchxh has posted my talk from C4, and if you would enjoy watching a talk without paying, you can watch it. It's on hype, and how I generate it, but it also touches on other topics concerning having your own software company, like making good software, bundling, getting into stores, having sex with cylons, &amp;c.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.viddler.com/explore/rentzsch/videos/4/" target="other"&gt;Watch it!&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://icanhascheezburger.com/" target="other"&gt;Or don't.&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/11049281-1326760229234214113?l=wilshipley.com%2Fblog' alt='' /&gt;&lt;/div&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/1326760229234214113'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/1326760229234214113'/><link rel='alternate' type='text/html' href='http://wilshipley.com/blog/2008/02/my-c41-talk.html' title='My C4[1] Talk...'/><author><name>Wil Shipley</name><uri>http://www.blogger.com/profile/04429428424208378747</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='07967666282086327688'/></author></entry><entry><id>tag:blogger.com,1999:blog-11049281.post-3373294962908008560</id><published>2008-01-15T21:07:00.000-08:00</published><updated>2008-02-04T19:32:37.069-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='mac community'/><category scheme='http://www.blogger.com/atom/ns#' term='interface design'/><category scheme='http://www.blogger.com/atom/ns#' term='business'/><title type='text'>MacBook Air Haters: Suck My Dick</title><content type='html'>I thought of a lot of titles for this post, but, really, the first one that came to me seems the best.&lt;br /&gt;&lt;br /&gt;I've read nothing but whining about the MacBook Air on Mac news sites since it came out this morning. Honestly, I just want to shake these people. Not, like, shake some sense into them, but shake them like you're not supposed to shake a baby.&lt;br /&gt;&lt;br /&gt;The criticism all basically goes like this: "It's not like a MacBook Pro!"&lt;br /&gt;&lt;br /&gt;No, really? Seriously? I mean, they introduced this new product, and it doesn't have the same specs as the MacBook Pro? God, that is bizarre. I wonder why they gave it a new name, and continue to sell the MacBook Pro, then, if it's not going to be exactly the same. I mean, that hardly makes sense, does it?&lt;br /&gt;&lt;br /&gt;Ok, fine, there are some people who want, like, an extra battery Pack. But let's admit amongst ourselves that the overwhelming majority of people out there have never pulled the battery out of their existing laptops, and didn't even know or care that it comes out. In fact, if something goes wrong with their battery, this majority -- whom we'll call "NORMAL PEOPLE" for convenience -- will just take the damn machine to the store and get it fixed, whether it's user-serviceable or not. Because we don't want to hassle with it.&lt;br /&gt;&lt;br /&gt;And there are people out there who do video editing on their MacBooks and want FireWire. Great! I respect your choice! You should buy a MacBook! It's an awesome machine! If you want to do that! Which I don't!&lt;br /&gt;&lt;br /&gt;I've read journalists complain that you can't get at the hard drive in the MacBook Air. What? I have no fucking idea where the hard drive is in my MacBook Pro, and even if you drew me a damn diagram with labels and numbers and gave me a replacement drive I wouldn't open my machine even in exchange for a year with Zooey Deschanel. Ok, yes I would, but you get my point. I'm sorry, Zooey, I didn't mean it, baby.&lt;br /&gt;&lt;br /&gt;Some journalists get so close to the truth it hurts, yet miss the large print. "OMG! The unit is all sealed and self-contained like the iPod!"&lt;br /&gt;&lt;br /&gt;Yes... the iPod. That huge failure. Also, the iPhone. Stunning disappointment that it was. I mean, jeebus, why would Apple make ANOTHER device incredibly simple? Clearly the market has spoken, and it wants tons of ports and screws and geegaws and flippers... no, wait, no it doesn't.&lt;br /&gt;&lt;br /&gt;You guys are TECHNOLOGY JOURNALISTS. You are GEAR HEADS. There is no shame in this, but, come on, recognize that what you think is cool is NOT what my mom thinks is cool, or what an executive thinks is cool, or what a lawyer who just wants to write a deposition on her laptop thinks is cool.&lt;br /&gt;&lt;br /&gt;I'm a programmer. I just want a machine I can write software on. Once, I loved gadgets, too, but now I really just want a gadget that (a) works, and (b) is beautiful and easy-to-use. Sure, my iPhone doesn't have as many raw features as my lawyer's Blackberry + RAZR combined (she carries both). But I understand my iPhone, and I don't have to learn it, because it's learned me. I can take a photo in three seconds, and so can she (we tested) even though she'd never seen an iPhone before.&lt;br /&gt;&lt;br /&gt;I'm not the freak, here. In this one instance. I'm with the majority. All software developers should be hailing the advent of  the computer-as-appliance, because it means we'll be reaching into markets that are afraid of self-service machines.&lt;br /&gt;&lt;br /&gt;I can't take apart my Kitchenaid blender. If they come out with a new motor, I'm screwed. It's not upgradeable! And when the motor blows (as it DID... grrr), I have to send it back. I can't take apart my car. When Lotus came out with a bolt-on supercharger, I had to (gasp) take it to the dealer to have it put in. Somehow I survived.&lt;br /&gt;&lt;br /&gt;I don't buy a laptop because I want to replace its drive in a year. I buy it because it seems great and meets my needs today. If my needs magically morph over the coming year, I guess I'll sell it on eBay. Or pay Apple to throw in a different drive, or something. Honestly, I think we need to admit that just because machines get faster every year, doesn't mean that the majority of people need faster machines.&lt;br /&gt;&lt;br /&gt;In two weeks I'll be writing Delicious Library 2 on a MacBook Air, every day. Because it's simple and beautiful, and I crave those things.&lt;br /&gt;&lt;br /&gt;And all you haters can... well, buy one in six months, when you realize how nice it is.&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;Update 2/4/2008: My MacBook Air with 2GB of RAM and 1.8 GHz cores and the SSD compiles Delicious Library 2 from scratch in 1:59.4. My MacBook Pro with 3GB of RAM and 2.3GHz cores and an HD compile it in... 2:04.3.&lt;br /&gt;&lt;br /&gt;MacBook Air FTW.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/11049281-3373294962908008560?l=wilshipley.com%2Fblog' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/3373294962908008560/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=11049281&amp;postID=3373294962908008560' title='84 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/3373294962908008560'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/3373294962908008560'/><link rel='alternate' type='text/html' href='http://wilshipley.com/blog/2008/01/macbook-air-haters-suck-my-dick.html' title='MacBook Air Haters: Suck My Dick'/><author><name>Wil Shipley</name><uri>http://www.blogger.com/profile/04429428424208378747</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='07967666282086327688'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>84</thr:total></entry><entry><id>tag:blogger.com,1999:blog-11049281.post-7324027759449864806</id><published>2007-12-22T17:58:00.000-08:00</published><updated>2007-12-22T17:59:36.513-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='mac community'/><category scheme='http://www.blogger.com/atom/ns#' term='humor?'/><category scheme='http://www.blogger.com/atom/ns#' term='business'/><title type='text'>Are You a Genie in a Bottle?</title><content type='html'>Oh... &lt;br /&gt;You feel your brain's been locked up tight&lt;br /&gt;Writing good code at only at night&lt;br /&gt;Waiting for a job&lt;br /&gt;To challenge you&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;You're cracking your knuckles, trying to code my way&lt;br /&gt;But that don't mean I'll hire you right away&lt;br /&gt;Laddy, Laddy, Laddy &lt;br /&gt;(Lady, Lady, Lady?) &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Yo yo... &lt;br /&gt;Your mouth's saying hire me&lt;br /&gt;Oh woe...&lt;br /&gt;But my brain's saying let's see C&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;If you wanna work for me, laddy &lt;br /&gt;There's a price you pay &lt;br /&gt;I'm a stickler for design &lt;br /&gt;You gotta write code the right way &lt;br /&gt;If you want an ADA&lt;br /&gt;I can make your wish come true&lt;br /&gt;You gotta make a big impression &lt;br /&gt;I gotta like what you do &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;I'm a stickler for design, laddy &lt;br /&gt;Gotta write code the right way, money&lt;br /&gt;I'm a stickler for design, laddy&lt;br /&gt;Add, add, add, and then cut out&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;The iPhone's coming and AAPL's so not low&lt;br /&gt;One more release of Library to go&lt;br /&gt;Waiting for someone&lt;br /&gt;Who impresses me&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Fingers racing at the speed of light &lt;br /&gt;And not just because I'm in a Twitter fight &lt;br /&gt;Laddy, Laddy, Laddy &lt;br /&gt;(Lady, Lady, Lady?) &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Yo yo... &lt;br /&gt;I only have one engineer to go&lt;br /&gt;Oh woe...&lt;br /&gt;But I'm still going to hire slow&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;If you wanna work for me &lt;br /&gt;And then Apple someday &lt;br /&gt;I'm a stickler for design &lt;br /&gt;You gotta write code the right way &lt;br /&gt;If you want to get low pay &lt;br /&gt;I can make your wish come true &lt;br /&gt;Send me sample code, laddy&lt;br /&gt;And maybe I'll hire you&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;I'm a stickler for design, laddy &lt;br /&gt;Gotta write code the right way, money&lt;br /&gt;I'm a stickler for design, laddy&lt;br /&gt;Send, send, send your sample out&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/11049281-7324027759449864806?l=wilshipley.com%2Fblog' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/7324027759449864806/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=11049281&amp;postID=7324027759449864806' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/7324027759449864806'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/7324027759449864806'/><link rel='alternate' type='text/html' href='http://wilshipley.com/blog/2007/12/are-you-genie-in-bottle.html' title='Are You a Genie in a Bottle?'/><author><name>Wil Shipley</name><uri>http://www.blogger.com/profile/04429428424208378747</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='07967666282086327688'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-11049281.post-4430606081417953346</id><published>2007-12-18T04:45:00.000-08:00</published><updated>2007-12-19T17:56:34.618-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='mac community'/><category scheme='http://www.blogger.com/atom/ns#' term='stories'/><category scheme='http://www.blogger.com/atom/ns#' term='code'/><title type='text'>Transitions and Epiphanies.</title><content type='html'>It's been a crazy couple of weeks for me...&lt;br /&gt;&lt;br /&gt;First off, Lucas Newman is leaving Delicious Monster -- as of January 1 he will be an iPhone engineer. This is an amazing opportunity for him, one I would never ask a friend to pass up. We remain buddies, although I'm running around Zoka these last couple weeks telling every girl I see that Lucas was secretly super-hot for her and is leaving now, which I think is starting to annoy him. Although, honestly, they'll probably all end up throwing themselves at him and he'll end up on top, again.&lt;br /&gt;&lt;br /&gt;For those keeping score at home, this makes Mike Matas, Scott Maier, Tim Omernick, Drew Hamlin, and Lucas Newman that Apple has hired out of my employ. Yes, in fact, 100% of Delicious Monster's ex-employees are now working for Apple! You'd almost think Apple would start to pay me to train people for them. Oh, well. It's every kid's dream to work there, I can't say I blame them. Heck, I might work for Apple myself if they ever asked. And, like, wanted to give me EIGHTY ZILLION DOLLARS.&lt;br /&gt;&lt;br /&gt;Also, seriously, if you want to work for Apple, you MIGHT want to, you know... GET TO KNOW ME.&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;Mike Lee is staying at Delicious Monster -- for now... DUM DUM DUM! You have to figure he's playing the various Apple teams off each other -- when you work at Delicious Monster, you don't jump for the girl that asks you to dance. Mike's all: "CoreAudio? Don't waste my time, sweetheart." "OS X Server? I'm sorry, you're not even getting an interview." "Ali Ozer and Scott Forstall got into a fistfight over me at lunch today? Now, see, these guys understand what kind of ball we are playing."&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;I realized tonight that I had yet another problem with CoreData, and it was a doozy, and not something where I could just put a hack on it. In fact, it was indicative of a fundamental architecture mismatch that I've been struggling with since I started this project.&lt;br /&gt;&lt;br /&gt;So, this is a little vague, but I thought it might be important to document the process. Basically, when I bang up against a wall, I start looking bigger and bigger and bigger. Like, imagine I'm having trouble with a crumbling wall in an aqueduct -- my programmers brain does this: "Ok, why did I build this wall?" To keep the water in. "Why do I have water?" Because you need that to turn the water-wheel. "Is there some other way to turn it?" Not easily. "Why must it turn?" To power the grinder. "What needs grinding?" Corn. "Is there some other way to grind it?"&lt;br /&gt;&lt;br /&gt;I'll get to truly huge things, where I start asking if the world even needs an app that catalogs books and DVDs and now boardgames when we could all be under five feet of water in a few years. Then it's time to take a nap and wake up and start again.&lt;br /&gt;&lt;br /&gt;But my point is, you HAVE to question all the basic assumptions that led you to where you are, or you end up spending all your time &lt;i&gt;writing the wrong code&lt;/i&gt;. I have always said that if you give me a perfectly spec'ed out program (one with a spec that can actually work, that I'm not going to have to modify as I go along), I can write that program for you in days. Always. The problem with coding is (a) fighting with frameworks, and (b) trying to figure out how the program should look, work, and interact even as we code it.&lt;br /&gt;&lt;br /&gt;So we end up spending a lot of times fixing bugs in code that we really shouldn't have written in the first place -- code that doesn't really help the user, that just makes the app more complex, that is for a feature that never should have been put in, or is interacting with the user incorrectly and we're just putting spackle on a wall that's crumbling.&lt;br /&gt;&lt;br /&gt;So here I am, tonight, running into my 1,000th bug with the fundamental mis-architecture in CoreData, which is that  interacts with the UI layer and the disk layer / undo layer all using the same mechanism. They all rely on -didChangeValueForKey:, which is a huge mistake, because it means that, as a programmer, I can't sneak any data in -- I can't change a value without it creating an undo event.&lt;br /&gt;&lt;br /&gt;Consider if, for example, I had a clock and its hands were CoreData objects. As they move forward through time, their position updates, so I'd tell them to update. And each time I did, an undo event would get pushed -- so the user actually could undo time.&lt;br /&gt;&lt;br /&gt;This is obviously a contrived example, but it also points to the fundamental problem -- CoreData objects can't mix undoable and non-undoable changes.&lt;br /&gt;&lt;br /&gt;So I've been struggling for three years now, trying to bend and hack and cajole CoreData's undo architecture into allowing me to do some actions synchronously and some asynchronously. (For instance, obviously, once the program has downloaded a cover from Amazon in a background thread, you don't want to UNDO the download -- it's not actually a state change, it's just a cache change -- yet, by default we end up with an undo event on the stack, in the MIDDLE of whatever the user is actually doing in the foreground.)&lt;br /&gt;&lt;br /&gt;Fight fight gnash gnash complain complain. Tonight I hit on it. I needed to step back. Why isn't this working? Because undo wasn't designed this way in CoreData.&lt;br /&gt;&lt;br /&gt;Well, I have undo in Delicious Library 1. It's not "magic" like with CoreData, but it works. In fact, now that I am thinking about it -- I've spent months and hundreds of lines of code trying to get CoreData's "magic" undo to work, when, in fact, there are really only FOUR actions that are ever undone:&lt;br /&gt;&lt;br /&gt;1) Add a book -- undo to delete it&lt;br /&gt;2) Delete a book -- undo to add it back&lt;br /&gt;3) Change a property on a book, like its title or author -- undo to change it back&lt;br /&gt;4) Make a loan -- undo to return the book&lt;br /&gt;5) Return a book -- undo to re-make the loan&lt;br /&gt;&lt;br /&gt;That's... about it. SO WHY HAVE I SPENT ALL THIS TIME TRYING TO GET COREDATA'S MAGIC SYSTEM TO WORK?&lt;br /&gt;&lt;br /&gt;There's only five damn methods, at the top level, that need to participate in undo. It's pretty obvious I should be managing my OWN undoManager, turn off the one in CoreData, and just use CoreData for what it is EXTREMELY good at, which is minimal change tracking and fetching and storing data VERY VERY quickly.&lt;br /&gt;&lt;br /&gt;Suddenly all these issues I've been having disappear. I don't have strange extra undo events on my stack when I fault in an object, because although CoreData might think my object changed, it's not driving the undo manager any more -- and when it goes to save, it's going to quickly discover there's no real substantive changes and just discard the whole event.&lt;br /&gt;&lt;br /&gt;I don't have to try to work around some undo events by turning undo on and off, which required me to flush CoreData's transactions queue by hand, which was extremely sketchy because if you do it in some circumstances (eg, the middle of inserting a new object) the object will be corrupted.&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;I haven't started this yet -- I'll try it tomorrow. It's nice -- it'll pick up a bunch of the remaining issues I'm having in DL2, and should give us a good solid beta.  The important thing here is, I was just too married to part of the code. I was so into using CoreData's magic undo that I kept going farther and farther to make it work, when I really needed to say, "Ok, this doesn't work in this situation, I'm doing my own undo in 40 lines of code."&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/11049281-4430606081417953346?l=wilshipley.com%2Fblog' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/4430606081417953346/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=11049281&amp;postID=4430606081417953346' title='14 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/4430606081417953346'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/4430606081417953346'/><link rel='alternate' type='text/html' href='http://wilshipley.com/blog/2007/12/transitions-and-epiphanies.html' title='Transitions and Epiphanies.'/><author><name>Wil Shipley</name><uri>http://www.blogger.com/profile/04429428424208378747</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='07967666282086327688'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>14</thr:total></entry><entry><id>tag:blogger.com,1999:blog-11049281.post-7728157748076184531</id><published>2007-12-08T15:56:00.001-08:00</published><updated>2007-12-08T16:14:53.001-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='stories'/><title type='text'>Mr. Murray is Dead.</title><content type='html'>I killed him today - 10 minute ago.&lt;br /&gt;&lt;br /&gt;F.P. Murray Fuzzcat was a pure-bred Persian born to champions, bred by my sister (a veterinarian in California)  to show or sell. His nose was deemed "too large" at birth, so she gave him to me, for mere room and board. Later in life, my sister would inspect him and reverse her decision -- he had "grown into" his nose and could have been a champion, had I not already taken his manhood. (Thom-hood?) I didn't have any interest in going to cat-shows with my little guy, but it made me feel good to know that the blood of champions throbbed through his veins.&lt;br /&gt;&lt;br /&gt;When a cat dies you understand that most people, while being able to empathize with your pain, won't actually give a crap themselves. Murray didn't work for world peace. He built no homes for orphans, and his response to the Hurricane Katrina was indifference. He was a cat.&lt;br /&gt;&lt;br /&gt;But, still. There are lives he touched, especially mine. My only memory of Murray is purring. He was the purriest cat I have ever known. Years ago I would take business conference calls in bed when major clients wanted to chat at 7AM and I wanted to sleep until noon -- I would lie there with my cell phone talking with captains of industry while Murray sat on my chest and purred at me. One time a vice-president for McGraw-Hill interrupted the conference call and interjected, "Wil, are you on a motorboat?" I shit you not.&lt;br /&gt;&lt;br /&gt;When I was sad, Murray would lick my ears. When I was saddest, I would wake up and he'd be stretched out beside me, and his little paw would be resting in mine. When I'd wake up he'd sit up and purr at me, from just out of reach. I used to play chicken with him, and sit there staring at him and see how long he'd continue purring before I had to pet him. I'd always give up and give him a scratch before he stopped purring.&lt;br /&gt;&lt;br /&gt;Murray had great taste in women, and I trusted his judgment on which ones to date. Upon first meeting Murray, one of my favorite lovers remarked, "You are just a little lover, aren't you?" She honestly loved Murray more than me, I think, and I honestly understand why.&lt;br /&gt;&lt;br /&gt;Murray was a gentle soul. If I threw him in the bath for poopy-butt he'd just meow forlornly and try to leave -- I never got scratched by him so that it bled. He was so gentle with his claws that I would frequently forget he had them at all, and not clip them for years at a time, and then one day I'd notice they'd grown inches long and curved all the way around like the stereotypical wizened asian wizard.&lt;br /&gt;&lt;br /&gt;Murray was 18 years old, and his kidneys were in advanced failure. There is no cure except an experimental surgery which transplants kidneys from a young, healthy cat into my ancient one. I could not justify that in my head. Over the last two days he suddenly got much sicker, and I finally realized he was done.&lt;br /&gt;&lt;br /&gt;As I held him in the vet's office, they gave him a sedative and then the poison. I put my ear up to his nose so I could hear his breathing, and so he could smell earwax, which he really did love. I stroked his throat and with each little exhale I could feel the tiny rattle of a faint purr -- the last purrs he had in him &lt;b&gt;had&lt;/b&gt; to come out.&lt;br /&gt;&lt;br /&gt;My last words to him were, "Thank you, little guy."&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/11049281-7728157748076184531?l=wilshipley.com%2Fblog' alt='' /&gt;&lt;/div&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/7728157748076184531'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/7728157748076184531'/><link rel='alternate' type='text/html' href='http://wilshipley.com/blog/2007/12/mr-murray-is-dead.html' title='Mr. Murray is Dead.'/><author><name>Wil Shipley</name><uri>http://www.blogger.com/profile/04429428424208378747</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='07967666282086327688'/></author></entry><entry><id>tag:blogger.com,1999:blog-11049281.post-868996496130465716</id><published>2007-12-05T00:41:00.001-08:00</published><updated>2007-12-05T00:41:24.675-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='stories'/><title type='text'>On saying goodbye.</title><content type='html'>There are so many clichés that, as I grow up, I find are really true. And with every one, I go through the same process:&lt;br /&gt;&lt;br /&gt;"Gee, I've discovered this amazing and unique thing about humanity that no one has ever discovered before, but how can I express it in words... Hmm, well, in this case, I want to convey the idea that sometimes you want to express a sexual attraction to a person and have them confirm a reciprocal attraction, but you don't feel a level of attraction where you'd want to start anything long-term -- you just want an innocent exchange of physical compliments... If only there were some succinct way to say it, like, uh...&lt;br /&gt;&lt;br /&gt;"Oh, I know: A kiss is just a kiss.&lt;br /&gt;&lt;br /&gt;"Oh, wait."&lt;br /&gt;&lt;br /&gt;[It's another cliché that every generation thinks they're the first ones to feel every emotion, and have every idea.]&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;"Youth is wasted on the young," my mom used to always say to me, which made me want to smack her, because *I* was young, and I was hearing her basically saying that she wanted to suck the life-force out of me and horde it for herself. That ship has sailed, mom! (I already did the opposite to you.)&lt;br /&gt;&lt;br /&gt;Now I'm reasonably old, and I find myself thinking, "Damn, I wish I had all the time ahead of me that I had when I was 20, because I think I've finally started to figure out what life is about, and I was so miserable then, but now I'm worried that by the time I really get it down, I'm going to be enfeebled and not able to enjoy it... if only there were some succinct way to say this... some kind of saying... oh, wait."&lt;br /&gt;&lt;br /&gt;Damn you, mom!&lt;br /&gt;&lt;br /&gt;But the cruel irony of clichés is we're doomed to not understand them until the moment we re-coin them for ourselves. Just as you can't explain to someone why it's bad to stick their hand in a flame until they've actually felt pain, you can't explain love and loss and happiness and inner peace to someone who hasn't experienced those things, first-hand. And, by that time, their response will just be, "Duh, I know that &lt;b&gt;now&lt;/b&gt;, you should have told me a long time ago."&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;Recently I've been thinking about nature of loss, and how we all want to deny to ourselves that it will ever happen to us. We want to believe that every love is our last love, that our cat will outlive us, that our job will continue to be a perfect fit forever, that our health will continue until we drop dead, which we won't ever do anyway, and that our friends will never move away or betray us or simply grow more distant over the years.&lt;br /&gt;&lt;br /&gt;And, yes, all evidence points to the contrary. Most people think I'm morbid when I say, "You know, this relationship *will* end badly," and they won't discuss it further with me. But, honestly -- the &lt;i&gt;very&lt;/i&gt; best we can hope for is that our relationship will end with one of us dying. And, seriously, that's going to suck for both the dead guy &lt;i&gt;and&lt;/i&gt; the person left behind. Or we could hope to both die simultaneously, but, I dunno, that doesn't seem entirely awesome either. ("Hope you die when I do, honey! Good night!")&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;We are denial machines. This is what I've learned going to TED these last couple years -- there are several amazing talks on this, I'll point to this one by Michael Shermer on "Why people believe strange things":&lt;br /&gt;&lt;br /&gt;&lt;object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0" width="320" height="285" id="VE_Player" align="middle"&gt;&lt;param name="movie" value="http://static.videoegg.com/ted/flash/loader.swf"&gt;&lt;PARAM NAME="FlashVars" VALUE="bgColor=FFFFFF&amp;file=http://static.videoegg.com/ted/movies/MICHAELSHERMER_high.flv&amp;autoPlay=false&amp;fullscreenURL=http://static.videoegg.com/ted/flash/fullscreen.html&amp;forcePlay=false&amp;logo=&amp;allowFullscreen=true"&gt;&lt;param name="quality" value="high"&gt;&lt;param name="allowScriptAccess" value="always"&gt;&lt;param name="bgcolor" value="#FFFFFF"&gt;&lt;param name="scale" value="noscale"&gt;&lt;param name="wmode" value="window"&gt;&lt;embed src="http://static.videoegg.com/ted/flash/loader.swf" FlashVars="bgColor=FFFFFF&amp;file=http://static.videoegg.com/ted/movies/MICHAELSHERMER_high.flv&amp;autoPlay=false&amp;fullscreenURL=http://static.videoegg.com/ted/flash/fullscreen.html&amp;forcePlay=false&amp;logo=&amp;allowFullscreen=true" quality="high" allowScriptAccess="always" bgcolor="#FFFFFF" scale="noscale" wmode="window" width="320" height="285" name="VE_Player" align="middle" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer"&gt;&lt;/object&gt;&lt;br /&gt;&lt;br /&gt;And this one by Dan Gilbert on "Why are we happy? Why aren't we happy?"&lt;br /&gt;&lt;br /&gt;&lt;!--cut and paste--&gt;&lt;object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0" width="320" height="285" id="VE_Player" align="middle"&gt;&lt;param name="movie" value="http://static.videoegg.com/ted/flash/loader.swf"&gt;&lt;PARAM NAME="FlashVars" VALUE="bgColor=FFFFFF&amp;file=http://static.videoegg.com/ted/movies/DANGILBERT_high.flv&amp;autoPlay=false&amp;fullscreenURL=http://static.videoegg.com/ted/flash/fullscreen.html&amp;forcePlay=false&amp;logo=&amp;allowFullscreen=true"&gt;&lt;param name="quality" value="high"&gt;&lt;param name="allowScriptAccess" value="always"&gt;&lt;param name="bgcolor" value="#FFFFFF"&gt;&lt;param name="scale" value="noscale"&gt;&lt;param name="wmode" value="window"&gt;&lt;embed src="http://static.videoegg.com/ted/flash/loader.swf" FlashVars="bgColor=FFFFFF&amp;file=http://static.videoegg.com/ted/movies/DANGILBERT_high.flv&amp;autoPlay=false&amp;fullscreenURL=http://static.videoegg.com/ted/flash/fullscreen.html&amp;forcePlay=false&amp;logo=&amp;allowFullscreen=true" quality="high" allowScriptAccess="always" bgcolor="#FFFFFF" scale="noscale" wmode="window" width="320" height="285" name="VE_Player" align="middle" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer"&gt;&lt;/object&gt;&lt;br /&gt;&lt;br /&gt;There are others which I can't find at the moment -- I encourage you to look around. And maybe I'm doing a disrespect to the incredibly intelligent people who've written these talks by restating them in my own words, but, hey, that's how I do.&lt;br /&gt;&lt;br /&gt;So, the gestalt I got from TED was: we are "designed," as beings, to be unreasonably optimistic. That is, we have evolved an unrealistic optimism as a defense to the fact that everything good ends, and in fact ends badly. (By definition -- if we're enjoying something, we don't want it to end, but everything ends, and if we're not enjoying something, the good part has already ended, so QED.)&lt;br /&gt;&lt;br /&gt;As we stood erect and grasped things and used tools and grew our brains, we became self-aware, and then aware of the finite span of our happiness, and our genes faced a dilemma (evolutionarily speaking): our race could either be hopelessly discouraged by the tragedy of life, or we could be kept a little bit stupid so we wouldn't think about it. But this is a logical fallacy: a false dilemma -- there's a third route, which I believe evolution took: she gave us with a blind spot. We are, fundamentally, illogical when it comes to our expectations of happiness.&lt;br /&gt;&lt;br /&gt;There is another cliché of sorts, or perhaps more of an aphorism: "In 100 years everyone you love will be dust." This is simply a truth. But it's depressing. Right now your mind is busily throwing away that sentence. You are reacting to it as you would a bad smell. You might even be angry that I mentioned it. "Why are you burdening me with this? What the hell good did you just do me?"&lt;br /&gt;&lt;br /&gt;But I'm not burdening you, not really. You're not going to be thinking about that sentence tomorrow. It's your defense mechanism -- well, it is if you're a lucky, normal person. There are lots of depressed people out there, and they have trouble ever moving away from those thoughts, so, sorry to you guys, but I bet you've already thought of that one anyway.&lt;br /&gt;&lt;br /&gt;Let's consider depression, and also consider that the geniuses we revere today were generally very disturbed, unhappy people. Are we, in fact, evolved not to be too smart, because at some point when you crank up intelligence, you can't help but see past your blind spot, and start to notice that life is, in the end, futile? That no matter how much we struggle, we WILL lose everything; we will die. We will be alone when that happens.&lt;br /&gt;&lt;br /&gt;So what, you say? Why not just enjoy the here and now? But I say this is a sham. You're lying to yourself. Because if I told you, with certainty, that you were going to die in 10 minutes, you wouldn't try to enjoy the here and now. You'd be crushed. Paralyzed. You wouldn't say, "Oh, boy, I need to make sure I really enjoy those 10 minutes! I'm going to eat an entire cake, screw the calories! Then have sex without a condom!"&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;I want to note I'm using a generic "you" here, in part to represent myself. Please don't take offense. I'm not trying to pick on YOU you, in particular. Nor am I scorning humanity from some mighty perch. I'm part of this sham. I get up every day and struggle to convince myself that, for some reason, things are going to get better for me, when, by definition, all the available evidence (eg, my life so far) suggests that things will be as good for me as they have been, and no better.&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;Imagine a society of rational beings (without our blind spot for how bad life can be) came to Earth to observe us -- ignore that this race wouldn't have developed space travel because they'd all be too busy staying up until 4am taking bong hits and watching "Chuck" on NBC.com trying to forget how miserable they are.&lt;br /&gt;&lt;br /&gt;Now, imagine what they would think of our lottery. This alone pretty much demonstrates that we are unreasonable optimists. We know, KNOW, that the average person who buys lottery tickets will not hit the jackpot. That, in fact, they won't break even. Not "average" as in 51% of people -- we know the &lt;i&gt;vast&lt;/i&gt; majority of people lose. We KNOW, and in fact &lt;i&gt;are explicitly told at the point of sale&lt;/i&gt; that the odds are amazingly stacked against us, and we are pissing our money down a hole.&lt;br /&gt;&lt;br /&gt;And, in fact, even if we were to win, most of us understand the cliché that "money doesn't buy happiness." We've seen the human-interest stories on lottery winners and cluck-clucked over the statistic that &lt;i&gt;most&lt;/i&gt; of them report being &lt;i&gt;less&lt;/i&gt; happy after winning the lottery, and a large number go bankrupt within a few years.&lt;br /&gt;&lt;br /&gt;"But that won't be me!" we say. We are promised, &lt;i&gt;guaranteed&lt;/i&gt; by the seller and by every mathematician that our odds are exactly the same as everyone else's, yet we make up new rules for ourselves, in defiance of all logic, that say we're going to win, and moreover enjoy it when we do. Dammit.&lt;br /&gt;&lt;br /&gt;Because we need something to look forward to. We need the dream. Once, long long ago, there were two types of people: those who could fool themselves into thinking life was worth living, and those who couldn't. Needless to say, the second group died out really quickly. And the first group has had millions of years to perfect its technique for overlooking the bad in life.&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;Again, I'd like to interrupt myself and say, hey, unreasonable optimism is a really good thing for most of us, most of the time. I mean, I have nothing against being happy, whether it's reasonable or not. If you want to sing in the rain, well, it's a bit of a cliché, but I obviously have nothing against those. I will join you whenever I can.&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;But maybe -- and this is what I've been thinking about -- just maybe, we should be AWARE that we're fooling ourselves. Maybe we need to occasionally pull our heads out and do check on the actual position in the world, and say, "Yes, we need to believe in order to get out of bed in the morning, but we also need to sometimes consider reality from a very rational standpoint, and make sure we're merely singing in the rain, and not singing in a monsoon that is the precursor to a giant flood that's going to kill us unless we climb that hill over there right now."&lt;br /&gt;&lt;br /&gt;I feel that way about things I blog about, like global warming or war or politics, obviously. In general, we want to trust our politicians to take care of us -- we &lt;i&gt;need&lt;/i&gt; to -- but it is also our duty to examine the world closely every once in a while, and not be surprised if it's screwed up and needs another course correction before we get back to our comfortable denial. It's been needed many times in the past (this isn't even our first energy crisis this half-century), and we shouldn't feign surprise when it happens again.&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;So, yes, when I start a relationship, I tell the woman, hey, you know, in all likelihood this will end in tears. (You're sorry you're not going out with a prize like me, right? Mayhap you're surprised I'm still single at 38?)&lt;br /&gt;&lt;br /&gt;But, wait... in my defense (possibly weakly) I'll point out: ending in tears is not necessarily bad. I mean, it's just there. It's a fact that we will end, it's simple probability that we'll end unhappily, unless you want to redefine happiness. So, let's spend a &lt;i&gt;little&lt;/i&gt; time planning for it. Thinking about the possible endings. So we can mitigate the bad things that are fungible and probable, and go back to ignoring the rest.&lt;br /&gt;&lt;br /&gt;Let's spend some time getting me life insurance, so if I do die, you can continue to live in my house. Let's think about what we'd do if we simply grow out of each other, so we can be civil if it happens.&lt;br /&gt;&lt;br /&gt;And most importantly, let me say this to you -- my friend, my lover, my family -- in advance: if it does end, I want you to know I won't regret it. Because everything ends. We spend all our time denying it, and when it finally happens we think it's the biggest tragedy in the world. I know this -- I've lost or given up the most important people in my life several times now. Sometimes I lose sight of the fact that it was inevitable. And if we only let ourselves think about loss when it strikes, we're going to be overwhelmed.&lt;br /&gt;&lt;br /&gt;How many relationships end where one person says, "I'm sorry I ever met you?" I've never felt that way. I'm never sorry, because you obviously brought something into my life; that's why I invited you in, in the first place. I'm often sorry that it ended (and sometimes not), but I always knew the end was there. I don't like loss, but an ending doesn't negate all that was good.&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;"Goodbye is hard to say." You knew I was going to end with a cliché, didn't you? I think the person who coined that one was thinking the same things as I am now, and came to the same conclusions, maybe. And that person said, damn, blogs haven't been invented yet -- is there some snappy saying I can come up with that will be remembered by the next generation, so they can avoid all the heartache I had?&lt;br /&gt;&lt;br /&gt;Well, no. You can't avoid heartache. But you can understand that it's inevitable. And, sometimes, maybe that's what you need to hear. Yes, you're going to hurt. I'm sorry. Don't let it spoil everything good.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/11049281-868996496130465716?l=wilshipley.com%2Fblog' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/868996496130465716/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=11049281&amp;postID=868996496130465716' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/868996496130465716'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/868996496130465716'/><link rel='alternate' type='text/html' href='http://wilshipley.com/blog/2007/12/on-saying-goodbye.html' title='On saying goodbye.'/><author><name>Wil Shipley</name><uri>http://www.blogger.com/profile/04429428424208378747</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='07967666282086327688'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-11049281.post-6984489276322327423</id><published>2007-11-18T04:26:00.001-08:00</published><updated>2007-11-18T04:33:46.229-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='humor?'/><title type='text'>Movie Sequels We Need</title><content type='html'>Who doesn't love sequels? Certainly no red-blooded American. But, sadly, some movies never get sequels. Why? And what can we do about it?&lt;br /&gt;&lt;br /&gt;I think the only thing to do is for us to show truth to power, and point out these omissions. Maybe we can inspire some Hollywood hack who has lost his way. If even one sequel gets made that would have otherwise been an original idea, wouldn't it be worth it?&lt;br /&gt;&lt;br /&gt;So, here's my list of sequels I need to see:&lt;ul&gt;&lt;li&gt;&lt;i&gt;Independence Day 2: Oh Crap The Aliens Closed Their Firewall&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;I Also Robot, How You?&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;Lake Shmonsequence&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;The List Schindler Kept In His Other Pants&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;Fight Club 2: Helena Bonham-Carter Was Also Just in His Head&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;Vertigo 2: Carsick&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;American History XI&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;Three Kill a Mockingbird&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;Spider-Man 3: For Real This Time&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;Faster and Dumber &lt;/i&gt;or&lt;i&gt; Dumb and Furious&lt;/i&gt; (mash-up)&lt;br /&gt;&lt;li&gt;&lt;i&gt;Memento 2: More Super-Depressing Backward Stuff&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;A.I. 2: We Are Not Aliens We Are Super-Advanced Robots Read the Title Fucking Duh&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;The Treasure of the Sierra Madre 2: We Still Do Not Require Any Form of Identification&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;The Piano 2: More of That Guy's Penis&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;Triple Indemnity&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;The Santa Clause 3: Now You Have To, Let's Say... Jump Up and Down&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;Reservoir Puppies&lt;/i&gt; (prequel)&lt;br /&gt;&lt;li&gt;&lt;i&gt;The Sting 2: We Swear There Is No Twist Ending LOL&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;Braveheart 2: It's Different When You Know He's Crazy&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;Blade Runner 2: Just Kidding It's Really Just Another Cut of Blade Runner&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;W for Wowiamangry&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;Shrek 2 Oh Wait There Was a Shrek 2&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;Shrek 3 Also, I Guess, I Did Not See It&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;It Happened Again on a Different Night&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;The Conversation 2: Not That Crappy Movie with Will Smith That Was a Pretend Sequel&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;Garfield 3: 2 in the Head, 1 in the Chest&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;North by Northwest 2: If You See the Dairy Queen You've Gone Too Far&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;The Oranges of Anger&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;The Lion King 2: If I'm Lion I'm Dyin'&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;Big Fish 2: Now MY Son has Grown Up and He Hates ME for Being a Big Liar&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;Dial L for Larceny&lt;/i&gt; (prequel)&lt;br /&gt;&lt;li&gt;&lt;i&gt;Rosemary's Second Child Who Didn't Get as Many Photos Taken of Her&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;Pirates of the Caribbean 4: Actually We Just Explain the Previous Two Films&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;Taxi Driver 2: Crazy Taxi&lt;/i&gt; (in conjunction with Sega)&lt;br /&gt;&lt;li&gt;&lt;i&gt;Serpents on a Submarine&lt;/i&gt; or &lt;i&gt;Boas on a Boat&lt;/i&gt; or &lt;i&gt;Rattlesnakes on a Railroad&lt;/i&gt; or &lt;i&gt;Cottonmouths in a Car&lt;/i&gt; (pick only one)&lt;br /&gt;&lt;li&gt;&lt;i&gt;Glad I Ate Him&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;Blazing Saddles 2: Chapped Asses&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;The Princess Bride 2: Hollywood is Full of Whores&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;Sex and the City: We Really Aren't Very Sexy Any More, To Be Honest&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;The Gun Just Below That Top One&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;M.A.S.H. 2: Apparently Some People Have Forgotten That War Is Bad&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;E.E.T.: An Extra Extra-Terrestrial&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;Close Encounters of the Fourth Kind: We're Boning Aliens&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;Deliverance 2: There's a Lot of Nerves Back There, It Can Feel Kind of Nice&lt;/i&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;A Streetcar Named Desire 2: A Lamppost Named Phyllis&lt;/i&gt;&lt;br /&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/11049281-6984489276322327423?l=wilshipley.com%2Fblog' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/6984489276322327423/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=11049281&amp;postID=6984489276322327423' title='12 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/6984489276322327423'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/6984489276322327423'/><link rel='alternate' type='text/html' href='http://wilshipley.com/blog/2007/11/movie-sequels-we-need.html' title='Movie Sequels We Need'/><author><name>Wil Shipley</name><uri>http://www.blogger.com/profile/04429428424208378747</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='07967666282086327688'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>12</thr:total></entry><entry><id>tag:blogger.com,1999:blog-11049281.post-3147029296428814345</id><published>2007-10-18T03:26:00.001-07:00</published><updated>2007-10-18T04:19:00.569-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='mac community'/><category scheme='http://www.blogger.com/atom/ns#' term='random ideas'/><title type='text'>Video Game Ideas: iPhone SDK edition</title><content type='html'>We've got it! We got our SDK! Well, I mean, we don't HAVE it yet, but we have a promise, and a promise that times nicely with my being done with Delicious Library 2 and looking for something to do before I start on v3.&lt;br /&gt;&lt;br /&gt;So what are we going to do with it? Sure, we're going to port Peggle (from PopCap), the BEST GAME EVER, and I hope there's a native version of Xeno Tactics (please write me if you know who can make this happen).&lt;br /&gt;&lt;br /&gt;BUT WHAT ELSE! We need to take advantage of the incredible and unique features of the iPhone... So, I'm going to try something new in the comments, here, and encourage people to brainstorm with me, either with new ideas or refining previous ideas to make them more possible / more fun.&lt;br /&gt;&lt;br /&gt;Let's look at the perfect storm of features the iPhone has brewing:&lt;br /&gt;&lt;br /&gt;- Always on. No other handheld consumer device is always on. Laptops go to sleep, as do DS Lites. Always on means that if we write social software, our iPhone can find other iPhones to talk to &lt;i&gt;for us&lt;/i&gt; based on some criteria, and then notify us as it finds matches. Welcome to the REAL social, bitches.&lt;br /&gt;&lt;br /&gt;- Always with their masters. Nobody who owns an iPhone will venture more than 20 feet from it.&lt;br /&gt;&lt;br /&gt;- Pretty damn popular. Sure, there is an order of magnitude more DSes, but iPhone is growing at a crazy rate. I see several every day at the café that I actually did NOT buy!&lt;br /&gt;&lt;br /&gt;- A cool variety of inputs, including acceleration detectors and a touch screen.&lt;br /&gt;&lt;br /&gt;- Really good resolution. Fairly fast graphics for certain subsets of drawing, but not something that'll run Quake 3 at a billion FPS.&lt;br /&gt;&lt;br /&gt;- Great networking, including Bluetooth, WiFi, and Edge. (Bono to be included in the next version.)&lt;br /&gt;&lt;br /&gt;- Doesn't require a cartridge per app, like Gameboys or Sony PSPs. All apps are resident in the iPhone at once, and multitask, so having a few casual, silly apps is much more likely. (Eg, you don't have to scream to everyone in the café, "Hey, let's all put in our social networking cartridge so we can break the ice!)&lt;br /&gt;&lt;br /&gt;- Is not butt-ugly like Zune.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Idea 1) "My Pokémons, Let Me Show Them To You."&lt;br /&gt;&lt;br /&gt;Imagine carrying all your Pokémon (or Magic, or whatever) cards around on your phone, and you can show 'em off CoverFlow-style, baby. If some other iPhone-wielding fool wants to step to your deck, you press a button for a WiFi connection (or bluetooth, even) and get to use your fingers to point to cards you want and throw them out on the playfield - it gives card battles a real tactile interaction.&lt;br /&gt;&lt;br /&gt;How do your cards get in to the phone? Well, if you want to use existing cards, you scan them in with the iPhone's camera, and recognize the photo (compared to a database of cards) and add that physical card to the user's virtual collection.&lt;br /&gt;&lt;br /&gt;Finally, you can trade virtual cards to people around you. For extra style points, you could list a bunch of cards you want and cards you don't want, and other iPhone users running the same client would just get notified that a potential trade is in their area. I imagine Gabe at the opera (yes, I like to imagine him going to the opera, ok?) and suddenly his phone starts buzzing and someone wants to buy one of his chimpochocs. "Honey, shhhh!"&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Idea 2) "My zombie beats your werewolf. Or mates with him."&lt;br /&gt;&lt;br /&gt;Like with the Zombie mini-game on facebook, all iPhone users could have zombie (or mutant, or zombie-hunter, or whatever) avatars, and whenever two iPhones get too close, they start to battle. It'd be super-funny if they did this on their own, so you could just be walking along and you and another dude's phones would suddenly go batshit making fighting and gurgling noises.&lt;br /&gt;&lt;br /&gt;You'd win persistent points, which would be tracked by a central database on some website, so you could see who trumps who. Each time you fight someone who is stronger than you, and you lose, you wouldn't get points but you'd get some of their "DNA" on you, which your creature could incorporate to become stronger. But, there'd be diminishing returns for fighting the same person over and over -- essentially you get zero points for attacking a creature with the similar DNA, so you want to find diverse creatures. You'd be much better off wandering around downtown and fighting strangers, because then you'll get a lot more possible mutations for your creature, and a lot more points for victories you make.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Idea 3) "Screw Pokémon, We Make Our Own Collectible Card Game"&lt;br /&gt;&lt;br /&gt;Like idea #1, but rather than screw with licensing, we make up a new game that's like Magic meets Pokémon meets all those other game Richard Garfield wrote or inspired. BUT, and here's the cool part, we just make up the base system, NOT all the cards.&lt;br /&gt;&lt;br /&gt;Like, we say, "Oh, in this game there is POWER, and POWER can be fed each round into ABILITIES or SPELLS or ATTACKS, and the order in which these things happen is this and this and this."&lt;br /&gt;&lt;br /&gt;THEN, we let people invent their own cards (in a high-level language we invent), most of which will contain exceptions to the rules or things which change the rules, because that's what's actually fun about these games. There would be some ground rules for developing cards -- eg, you'd have to win some matches to earn the points to do it, and your total card strength would be limited by how many points you are willing to spend. And you'd have to incorporate "flaws" with abilities or affects, too, so nobody could just say, "This card is free to play and requires no power and stops time and all your hit points go away."&lt;br /&gt;&lt;br /&gt;Even so, obviously some cards would be unbalancing. So: there's a central repository for card ideas, and before ANYONE can play a card (in matches which count towards points) it has to be digitally signed by the repository. The community views new cards and votes on which ones should go "into production", (and which ones should be retired) and those cards are made available... BUT, you can't just buy 'em directly. There's a random element to getting cards, as there should be... one cool thing we could do would be to finally do what Garfield wanted, and have it so you win cards in battle. Like, the loser of a battle could decide if she wanted to allow the winner to pick her best card and get a two new cards randomly from the repository, OR just let the winner get a single new card from the repository herself, OR vote for a single card from the winner's hand to be banned from play. (This wouldn't immediately ban it, but at some point really unfair cards would get too many votes and leave circulation.)&lt;br /&gt;&lt;br /&gt;Players would also be able to create "testing decks" -- they could use any number of any cards, legal or not legal, BUT matches with those decks look different and don't give any points or ranking. And, like, the iPhone takes away some of the graphic glitz, so it's clear you're just beta-testing your deck, you're not REALLY playing.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Idea 4) "Gnip-gnop"&lt;br /&gt;&lt;br /&gt;If you go to the kind of parties I do, about twenty people at any gathering have iPhones. What if you made some fast, silly gams (drinking games, maybe?) involving the phones? For example, take something as simple as a ball bouncing between people -- you'd see it coming towards your screen, and you'd have to flick it away, and you could flick it towards other players, and they'd have to keep it going...&lt;br /&gt;&lt;br /&gt;Or, imagine an iPhone game where you do that old sliding-picture-puzzle thing, where each iPhone's screen shows a section of a larger picture, and then everybody has to move around and stand next to each other such that the puzzle is solved.&lt;br /&gt;&lt;br /&gt;You could do this with teams -- maybe have it so there's a message, and each iPhone shows a different letter, and you have to re-arrange the iPhones to figure out the message first.&lt;br /&gt;&lt;br /&gt;Or you could show pictures on everyone's iPhone, but only two of them are similar, and those two people have to race to touch their iPhones together before a timer expires... I wonder if bluetooth signal strength is detectable on the iPhone, or WiFi signal strength -- whether one could actually tell if two phones are in very close proximity vs. 10 feet apart.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Idea 5) "Flash Mob Friends"&lt;br /&gt;&lt;br /&gt;Not necessarily a game... imagine tying in to the iPhone extension that can tell where your iPhone is based on the cell towers around it, and using that as a way to gather groups. You could have a tiny app that simply has a button for what kind of group you would like to hang with, and others in your group would be notified if they are in a similar mood.&lt;br /&gt;&lt;br /&gt;For instance, if I'm hungry, I could have a group of friends I sometimes eat with. So I press "Dinner Friends" and I go to Saluté, and everyone in that group can see where I am and that I am, in fact, actively getting dinner and would like company. They can IM me or just show up.&lt;br /&gt;&lt;br /&gt;This is a lot like what I do right now for dinner, actually, except right now it's more aggressive -- I have to page like 20 people with "Hey, getting dinner, what up?" and they have to actively turn me down "OH, sorry, just ate." I think it'd be easier on everyone if it were more passive, "Hey, everyone, getting dinner, if you are hungry and available then ping me, otherwise cool." And without the urgency of a page -- just a status you could check, like in Twitter or iChat.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/11049281-3147029296428814345?l=wilshipley.com%2Fblog' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/3147029296428814345/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=11049281&amp;postID=3147029296428814345' title='70 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/3147029296428814345'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/3147029296428814345'/><link rel='alternate' type='text/html' href='http://wilshipley.com/blog/2007/10/video-game-ideas-iphone-sdk-edition.html' title='Video Game Ideas: iPhone SDK edition'/><author><name>Wil Shipley</name><uri>http://www.blogger.com/profile/04429428424208378747</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='07967666282086327688'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>70</thr:total></entry><entry><id>tag:blogger.com,1999:blog-11049281.post-2518147644773667669</id><published>2007-10-11T08:50:00.001-07:00</published><updated>2007-10-11T08:50:46.394-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='mac community'/><category scheme='http://www.blogger.com/atom/ns#' term='business'/><title type='text'>Open systems, closed systems, and the future of Apple TV.</title><content type='html'>I own an Apple TV. (Yah, I'm the one.) I turned it on when I first got it, thought it looked really pretty, then turned it off and never touched it again.&lt;br /&gt;&lt;br /&gt;BUT! I think Apple TV will be an amazing device, and a massive success for Apple... after they make a few changes.&lt;br /&gt;&lt;br /&gt;Why did I turn mine off? Well, because in my TV room I also have a Mac mini hooked up to a 2TB drive. The Mac mini runs Front Row, just like the Apple TV, so it could be looked as a more-expensive version of the same device.&lt;br /&gt;&lt;br /&gt;However, the mini also runs iTunes, so I can buy new shows on the same system on which I'm watching TV. With the Apple TV, I have to have my laptop downstairs and turned on, and buy and download a new movie on the laptop before I switch over to my Apple TV to watch it. Clunky! (The situation is worse if you have a Mac Pro with your media on it -- what are you going to do, run upstairs to the computer room every time you want to buy a song or show?)&lt;br /&gt;&lt;br /&gt;And because the mini has a huge drive hooked up to it, it also acts as a content server to the rest of my house, so I can have a unified home for all my music and TV shows and movies, whether ripped or bought from iTunes -- it's my "Windows Home Server" without the Windows. Unlike with the Apple TV, which can't have an external disk, I never have to bring a second system into the equation, so the mini ends up being &lt;i&gt;cheaper&lt;/i&gt; than the Apple TV, because the Apple TV &lt;i&gt;requires a separate computer&lt;/i&gt;.&lt;br /&gt;&lt;br /&gt;And now I don't even have to bother storing my movies on my laptop, which is great, because my drive is already pretty damn full of porn. Uh, I mean builds of Delicious Library 2. In fact, nowadays if I want to buy a new movie and I'm not downstairs, I remotely access my mini from my laptop using Apple Remote Desktop and buy it from the version of iTunes running on the mini, so it's right on my server where I want it. Again, possible because the mini is running a full OS, not just Front Row.&lt;br /&gt;&lt;br /&gt;Note that a lot of the movies and TV shows I want to watch aren't from iTunes -- but since the mini is an open system, I can download &lt;a href="http://www.perian.org/" target=other&gt;Perian&lt;/a&gt;, an open source QuickTime add-on, and play movies AVI, FLV, MKV, DivX, and a billion other gibberish words. Hell, I don't even know what an FLV is. But, the point is, some of the content I want to play is in these formats, and Apple doesn't support them in QuickTime natively, so I can't play them on my Apple TV, since it's a closed system.&lt;br /&gt;&lt;br /&gt;If a friend brings over a DVD, I just pop it in the mini and we watch it. The DVD player under OS X has a much nicer interface (and remote!) than any other player I've had, so I put my super-expensive multi-region player into cold storage. The Apple TV doesn't have a DVD drive, and you can't hook one up, since it ignores external USB devices.&lt;br /&gt;&lt;br /&gt;For my personal DVDs, I can rip them using &lt;a href="http://handbrake.m0k.org/" target=other&gt;Handbrake&lt;/a&gt; and store them in my Movies folder, and Front Row magically finds them! No more pawing through stacks of DVDs! I finally have a DVD jukebox, the ultimate geek dream. The Apple TV doesn't allow me to install any third-party software. Heck, I can't even rip my CDs on the Apple TV, since it doesn't run iTunes and doesn't have a CD drive. &lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;To sum up: Apple TV doesn't allow developers to get at its UNIX underpinnings. It doesn't allow for modifications of its system software. It doesn't allow people to hook up an external disk or a DVD drive. It's a completely closed system. And, as of right now, it's pretty much a failure.&lt;br /&gt;&lt;br /&gt;Apple took a guess as to what features the market would want, and because Apple didn't allow for third parties to tweak and optimize what their system does, their guess had to be perfect the first time. It wasn't, and the Apple TV stays off in my house.&lt;br /&gt;&lt;br /&gt;There appears to be a battle being fought inside Apple, on whether Apple will be a company that provides solutions or provides tools. iTunes and Front Row are solutions -- really great solutions, sure. They are very friendly and they solve very specific problems beautifully. But they aren't particularly extensible by themselves. We can't make new functionality with them. (Note that if we have access to the underlying machine, as we do with the Mac mini, we are given the tools to modify these solutions -- we can make Front Row play MKV files by adding QuickTime components, even though it was written before MKV existed. We can make iTunes play WMVs.)&lt;br /&gt;&lt;br /&gt;Having a system be open, having it able to freely accept peripherals and new programs, turns it into a tool &lt;i&gt;as well as&lt;/i&gt; a solution. Each customer can decide what she wants the system to be, and developers can create new solutions -- and if those solve compelling problems, the entire system will be that much more successful. And, at the end of this cycle, the makers of the original tool can integrate these third-party solutions, so the tool grows for everyone.&lt;br /&gt;&lt;br /&gt;The amazing thing about the Mac mini vs. the Apple TV is it perfectly encapsulates the debate between providing solutions or tools to your customers. They are very similar boxes, from a raw-capability point of view, but one was closed and the other open. The Apple TV is a solution, and right now it's desperately searching for people who have the problem it solves.&lt;br /&gt;&lt;br /&gt;With the Mac mini, Apple provided us with a mix of solutions (iTunes, Front Row, etc) and tools (expandability, compilers, access to UNIX, access to plug-in directories). And, as a set-top box, the mini is incredible. Now, obviously, I have no idea what the mini's sales numbers are, and Apple hasn't really pushed the mini as a set-top box, and it does cost more than the Apple TV, blah blah blah... but it's clear to me that if the Apple TV did what the mini does, the Apple TV would be a GREAT set-top box and home server. It would own the Microsoft Home Server so hard that Ballmer would wake up with a sore back.&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;Why doesn't Apple just fix their solutions themselves, you say? If we all want MKV movies so much, why doesn't Apple just include support for it?&lt;br /&gt;&lt;br /&gt;Well, first off, they probably should, in this particular example. But Apple only has so many engineers on QuickTime, and besides it may not be particularly popular to add support for bizarro file formats from other companies, especially when Apple is pushing MPEG-4 (aka QuickTime) as the One True Wrapper.&lt;br /&gt;&lt;br /&gt;Second off, third parties can afford to sometimes make very limited or kind of half-baked solutions to dip a toe into the water, and if those are popular they can be fixed up later. Open Source projects don't make headlines in the NY Times when they push a major release that has some bugs, so we collectively get to invent a TON of different things let the market figure out which ones are pursuing. Consider the original CoverFlow, which was originally just a (really cool) demo by a third-party, in search of a problem to solve. Now Apple's bought it and put it into everything it ships except for iPod socks.&lt;br /&gt;&lt;br /&gt;Apple can't anticipate every change that is coming, or which changes will end up being popular. No, I don't think they should give up trying to do so, but I &lt;i&gt;do&lt;/i&gt; think they should share the burden. For instance, I've never seen an "FLV" file. Let's pretend for a minute that Apple did spend a bunch of time writing an FLV component for QuickTime, instead of speeding up h.264 encoding or something. And then, it turned out basically nobody used FLV, and Apple wasted their time and lost other neat functionality because of it.&lt;br /&gt;&lt;br /&gt;Now, the nice thing about FLV support being add in Perian is that Apple essentially has a bunch of suckers (I use the term lovingly) taking all the risk for them. If nobody cares about third-party movie formats -- well, Apple didn't spend any time on it. Shrug! If EVERYBODY cares about them -- well, there they are! Go download 'em! In fact, hey, these are open source -- Apple could just start bundling with them. No effort spent on Apple's part, but their marketshare just got a lot bigger.&lt;br /&gt;&lt;br /&gt;This is the beauty of open systems. Apple has a ton of very talented designers and very smart engineers. But they shouldn't have to be the ONLY smart people in the world, who must anticipate everything every customer might ever need. It's asking too much.&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;This whole post is ironic because Apple pays its AppleScript evangelists to say exactly what I'm saying, but back to us developers: Add scriptability to your apps! You can't anticipate everything the customer will want, but you can make your app into a tool! Allow other vendors to tie into your system and everyone wins!&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;I expect Apple will rev the Apple TV soon. One thing they could announce is that you can now rent movies over the internet, or maybe they'll announce you can access the iTunes store from directly inside Front Row. Either of these would be nice, sure. But they'd just be more pre-made solutions -- maybe they'd be popular enough to make the Apple TV a decent success, maybe not. But we would never know what Apple TV &lt;i&gt;could&lt;/i&gt; be.&lt;br /&gt;&lt;br /&gt;What I want Apple to announce is that they are merging the Apple TV with the Mac mini, and making it a hybrid closed/open system - a machine that boots into Front Row but can be used as a standard computer if you press some magic keys. A turn-key solution that can be opened up by advanced users and developers. The first mainstream consumer device that is infinitely hackable.&lt;br /&gt;&lt;br /&gt;The world is waiting for such a product. Apple's the company to do it.&lt;br /&gt;&lt;br /&gt;Set me free inside an Apple TV, see what I do for you.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/11049281-2518147644773667669?l=wilshipley.com%2Fblog' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/2518147644773667669/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=11049281&amp;postID=2518147644773667669' title='16 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/2518147644773667669'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/2518147644773667669'/><link rel='alternate' type='text/html' href='http://wilshipley.com/blog/2007/10/open-systems-closed-systems-and-future.html' title='Open systems, closed systems, and the future of Apple TV.'/><author><name>Wil Shipley</name><uri>http://www.blogger.com/profile/04429428424208378747</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='07967666282086327688'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>16</thr:total></entry><entry><id>tag:blogger.com,1999:blog-11049281.post-4686967194611815234</id><published>2007-10-02T02:06:00.000-07:00</published><updated>2007-10-02T02:23:58.781-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='random ideas'/><title type='text'>Video Game Idea: "Space 911"</title><content type='html'>Themes explored: when does the player feel violence necessary, how do people's backgrounds influence this decision, and (major theme) that you HAVE to make a decision on this point, that you can't decide not to decide because reasonable people disagree and people will be hurt or happy either way.&lt;br /&gt;&lt;br /&gt;Setting: you inherit a rescue-and-treat space facility from someone (your dead, estranged dad?).  You are put in charge fresh out of med school.  At the start of the game you are presented with a modified hippocratic oath which you must "sign" to go forward -- promising to "do no harm" before you can play the game.&lt;br /&gt;&lt;br /&gt;Your base is a space hospital that is a stationary (at least at first) facility with a space ambulance / boarding vessel that is surprisingly tough.  The hospital is asked to help in all kinds of space emergencies -- it's not just a hospital, it's a hostage rescue, fire-fighting, plague-containing, pirate warding all-purpose help-people-out kind of place.&lt;br /&gt;&lt;br /&gt;The staff of the hospital consists of a handful of doctors who go on missions with you.  What's special about these doctors is they've all joined the space hospital guild, which has a secret technology which only its members get, which enables them to magically teleport back to the hospital before they sustain enough damage to die.  Thus, they are (for our game purposes) unkillable, but they aren't unstoppable -- you can't continue a mission if everyone gets killed and sent back to base, duh.&lt;br /&gt;&lt;br /&gt;We want to keep the number of doctors small so the player gets attached to them (instead of having a party of zillions where you plug in and out faceless, nameless numbers) and feels that developing them is worthwhile -- trading them in on better models isn't an option. Also, when one of them quits in disgust, it should really hurt.&lt;br /&gt;&lt;br /&gt;Big plot: Not-so-friendly, very advanced, very xenophobic aliens (a la Heechee/Gateway books) have decided the easy way to ensure their dominance is to spend several milleniums locked inside a giant time capsule and wake up occasionally, taking the good ideas from all life forms that have evolved, and then wiping them all out and letting new ones start.&lt;br /&gt;&lt;br /&gt;The first thing that happens before they wake up is thousands of automated probes are sent out to all inhabited planets, and the probes start subtly screwing with the creatures on the planets to discover their strengths and weaknesses.&lt;br /&gt;&lt;br /&gt;Phase one is the probes start dredging up each society's long-dead plagues and trying them out.&lt;br /&gt;&lt;br /&gt;So, after a long period (thousands of years) of relative peace, the player is born into a galaxy that, for the last hundred years is falling increasingly into war and savagery, because the delicate balances of societies are being screwed up. Eg, planets that have become specialized in, say, robot creation, and don't even bother farming any more will find that they are all starving when the farming planets that supply them are attacked by plagues, and so in desperation they'll try to annex other farming planets that other advanced societies are depending on.&lt;br /&gt;&lt;br /&gt;Plagues are becoming more and more common, and each plague is getting nastier than the last, because the probes are learning which ones work best.&lt;br /&gt;&lt;br /&gt;In phase two the probes start exchanging information, and so plagues can't be contained any more, and need to be cured or the galaxy will be wiped out.&lt;br /&gt;&lt;br /&gt;In phase three the probes start creating new plagues based on what they've learned, and so the player needs to find a supercomputer that can come up with cures as fast as the plagues are being changed, and get societies plugged into the computer.&lt;br /&gt;&lt;br /&gt;Further phases (not finished yet) involve the aliens actually waking up and kicking butt, which the player will finally figure out and have to deal with.&lt;br /&gt; &lt;br /&gt;Missions vs. plot: 911 calls come in, and each hospital (there are others in the universe) will claim them depending on the hospital's effective range and the mission distance. After a while any given call will either be claimed or just time out. The player should aim for 100% of the calls in her range to be claimed by someone, but since there are other hospitals she won't have to do all of them herself, either.  But if the player ignores too many in a row, the other hospitals will get piled up and calls will start going unanswered. (Simulate this for real or fake it?)&lt;br /&gt;&lt;br /&gt;A lot of the missions will be randomly generated, non-plot missions. However, many of these, if you succeed at them, will be changed by the game into plot missions. Eg, if you answer a pirate attack call and save the captain, it may just so happen that captain saw something anomolous in another sector that you should check out blah blah blah.  If the captain dies, he didn't see anything, but maybe the next pirate call will contain the lucky captain who did?&lt;br /&gt;&lt;br /&gt;The basic idea is, no plotline can be "lost" by the player failing a mission that leads to it, because we just define the missions the user failed to not actually be part of the plot, and only the ones she succeeds at are made available to the game's mission engine for inclusion in the plot.  (Eg, the game engine needs a captain for the next part of a plot point, so it waits until the user gets one in the hospital, either as a walk-in or a rescue or what-have-you.)&lt;br /&gt;&lt;br /&gt;There will also be walk-in patients all the time, some of whom may have interesting plot points but a lot of whom will just be background noise. The user shouldn't have to do anything with the walk-ins unless she wants to.  (Eg, they'll automatically be treated, billed, and released unless there is something really unusual about them.)&lt;br /&gt;&lt;br /&gt;Note that the plagues are sort of background noise at the start of the game -- the player has known of quaranteened planets all her life, and doesn't think there's anything odd about it.  New plagues popping up is just a fact of life, just another kind of mission.  But early in the game wise authority figures start pointing out that the universe wasn't always this way, that there are statistically too many plagues right now, that something is really wrong with the whole universe.  Of course, there will be lots of paranoid theories of what that thing is that's wrong -- some will feel mankind has just lived to the end of its useful life and is dying of "old age", others will feel it's the inevitable triumph of microorganisms over macroorganisms that some people today think is inevitable, etc.&lt;br /&gt;&lt;br /&gt;Actually, it should be at the start of the game that whoever who left you the hospital also leaves you a note that tells you that something isn't right in the universe, and that although the old hospital chief couldn't figure it out, she did start to figure out who had the right ideas, and that the player should contact Professor So-and-so as soon as possible and resume her quest to set the universe right.&lt;br /&gt;&lt;br /&gt;Prof. So-and-so should probably be already dead, but there should be clues to someone else who can start explaining what's going on in the world.  (This first plot person should probably only explain give information about the plagues and suggest ways to combat them, and hint that there must be a darker force doing this but not have any idea what or who it is.)&lt;br /&gt;&lt;br /&gt;Mission structure: When the player goes out on missions (most of the game) she'll enter what are usually high-risk areas (often with combat going on) and have to make decisions about what kind of approach to take.&lt;br /&gt;&lt;br /&gt;For example, if a pirate ship has boarded and taken over a freighter and has hostages, and the space ambulance docks, what will the player's boarding party do? THIS SHOULD BE UP TO THE PLAYER.&lt;br /&gt;&lt;br /&gt;Some valid things to do:&lt;br /&gt;- kill every pirate, then heal all the wounded merchants. &lt;br /&gt;- heal wounded merchants as a priority but kill pirates if/when they interfere.&lt;br /&gt;- sneak around and get all the wounded merchants out and treated without killing any pirates, letting the pirates have the ship but not the people.&lt;br /&gt;- heal pirates and wounded merchants, stun pirates who resist, let pirates have ship&lt;br /&gt;- stun all pirates, heal everyone wounded, give ship back to merchants.&lt;br /&gt;&lt;br /&gt;Sure, the last one might sound the best to a lot of people, but it would also be the hardest -- the pirates don't want to be stunned, they're fighting with live ammo, and if they kill your team you lose the mission because you're teleported back to base and they'll surely get away before you get back to them.&lt;br /&gt;&lt;br /&gt;Also, MORE IMPORTANTLY, people ARE DYING as you are traipsing around the ship. When you board a ship there will be people actively bleeding out, some of whom you can treat and stabilize but some of whom will require a doctor to monitor them or they'll just plain die.  So, as you go through the ship, you'll have to make decisions every time you encounter a wounded person (pirate or merchant) -- "Can I spare another doctor to look after this person, will I be able to finish the mission with one less gun?"&lt;br /&gt;&lt;br /&gt;Note that you WILL be able to group critical patients together and have a single doctor look after multiple patients, but hauling them around takes time and other people might be dying elsewhere, and also it gives the pirates more time to secure the ship. (Say it takes the pirates a while to get the engine codes cracked, so there's a period of time after pirates take a ship where they can't fly it, and that's how you managed to dock with them -- they are dead in the water for a while.)&lt;br /&gt;&lt;br /&gt;There should be valid reasons presented to the player why she might want to do the mission each different way -- for example, the "kill-all-pirates" approach would result in the pirates having the ship for the least amount of time, and thus there would be less chance for them to actually get the engines restarted and actually make off with the booty.  (Note that, since your ship is docked with the freighter, you can still take off and get all the wounded off after the pirates crack the engines, but you'll have a limited time before they go into hyperspace or your ship is scuttled and you're toast.)&lt;br /&gt;&lt;br /&gt;Each approach should also effect how pirates in the area (and others) treat you in the future -- if you kill all pirates, then pirates will start to fear you and piracy will decrease, but when pirates do strike they'll be better armed, in greater numbers, and they'll attack you without mercy.&lt;br /&gt;&lt;br /&gt;On the other hand, if you never kill a pirate they'll only stun your crew to keep them out of their way.  And, if you actually heal pirates but otherwise don't interfere with them, then they'll completely ignore you (like the borg) and just let you go about and collect bodies and treat people (like the Red Cross during an ideal war).&lt;br /&gt;&lt;br /&gt;Piracy is a big problem now because entire planets are starving and so all space cargo has become incredibly precious.  There should be some sympathy for the pirates: "Our people are starving, we only want to get them resources so they aren't wiped out," but it shouldn't be complete, eg, they should still have killed innocent merchants and they are still taking resources from other groups that may be equally hard off.&lt;br /&gt;&lt;br /&gt;CAST:&lt;br /&gt;&lt;br /&gt;&lt;b&gt;RODOP7&lt;/b&gt; ("Dr. Roboto"): Giant, ugly, scary, loud (clanking) Robotic Doctor Prototype that is a couple hundred years old and the oldest healing droid currently in use. It's so antiquated that it doesn't have the ability to synthesize speech, it can just replay back 20 or so phrases that it has on "tape", like, "X is in trouble, you need to help him," and "Greetings, I am here to heal you, please relax."&lt;br /&gt;&lt;br /&gt;The catch is Dr. Roboto has evolved a form of intelligence over the years, and so he "feels" very strongly that no human should ever die, that all men who are hurt need saving. He tries to express this by stringing his pre-recorded messages together in new ways, and as the game goes on he'll learn to play only parts of his messages (separated at punctuation points?) and string those together to express what he "feels". This will surprise the player, who after the first 100 times he hears the same sentences will assume Dr. Roboto is just an unthinking droid, just as everyone else inside the game believes.&lt;br /&gt;&lt;br /&gt;Hideous radial arm saws on him. Patients are afraid of him. Rumors abound that he's a killer, or that he's just an organ-salvage droid for patients that are terminal, so nobody wants him to operate on them. He's also the best trauma surgeon that exists. HE CANNOT BE ORDERED TO HURT ANYONE.&lt;br /&gt;&lt;br /&gt;At some point the player should find an encyclopedia entry about droid doctors: "Although robotic assistants have existed in the operating theatre since at least the diaspora, for the purposes of this article a robotic droid is defined to be a unit that is ambulatory, self-determining during a single operation, and self-contained. The first of these were created by X in Y, the Robotic Doctor Prototypes. Prototypes 1-3 had coordination problems and did not understand commands well, and as such got a bad reputation for losing patients. Although the fourth had these problems corrected, it was deemed a failure because it wasn't self-determining enough (fix wording!). The fifth had a very complex morality model that ended up being nondeterministic, and after being forced to make some moral choices that would be difficult for even a human doctor it got into a bad state and became, to anthropomorphize, a homocidal killer, further tarnishing the reputation of robotic doctors. It was later lobotomized and used simply as an autopsy droid and occasionally to harvest organs from recently-deceased patients. For the sixth prototype they ripped out the complex morality and put in a very simple logical routine -- always try to save anyone wounded, but always ask first. Strangely, there are no records of the tests or outcome of the sixth prototype, or whether it was ever put into active service. It is also unknown how the seventh unit varies from the sixth, but the seventh was the first robotic member of the relatively new space-rescue guild, and it is still in active duty at a backwater hospital, kept running as a curiousity and tourist attraction despite its incredible simplicity compared to modern droids.&lt;br /&gt;&lt;br /&gt;After that [much better droids were built, much sleeker and more capable, blah blah]"&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Subcloud LRLLRRLRLRRRLLRRR&lt;/b&gt;: Like a little raincloud. Can't talk, understands English perfectly. Operates by entering the patient through the lungs and using its particles like nanomachines. Incredibly good at viruses, cancer, poisons, not so good at massive trauma. Incredible at stabilizing patients, though (can physically just remove shock chemicals from bloodstream).&lt;br /&gt;&lt;br /&gt;Communicates by forming itself into one or two symbol "emoticons". Color indicates whether it is asking a question, making a statement, or giving an order. So, a red up-arrow would mean, are we going up?&lt;br /&gt;&lt;br /&gt;Name comes from the fact that all members of its species are actually part of the same cloud that can split into two at any point, and so they just name themselves by specifying their last position in the binary tree. THEY AREN'T TELEPATHIC. When two members of the species meet, they reform into a new cloud, exchange memories, and then (usually) split off again. Whichever subcloud was highest and leftmost in the tree is used as the basis of the new name, and they add L and R to that. (Thus, the player's cloud may change names during the game.)&lt;br /&gt;&lt;br /&gt;Is willing to kill, but obviously is limited to choking and poison attacks. Since it floats it can do cool recon, but it can't pick anything up.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;The Twins&lt;/b&gt;: two bodies, one brain, both bodies talk simultaneously but always express two sides of one idea, like:&lt;br /&gt;&lt;br /&gt;"This mission will be very dangerous."&lt;br /&gt;"The rewards for this mission are great."&lt;br /&gt;&lt;br /&gt;Thus, it's hard to pinpoint exactly how it feels about any issue. [It's like Andrew at Omni.]&lt;br /&gt;&lt;br /&gt;They can only be commanded to do things as a pair, even though there are two of them.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Lt. Indian&lt;/b&gt;:  A human who experiences time in a "byte-swapped" manner from the rest of us. That is, in roughly ten-minute chunks, he experiences the future first and then now later. The result is he assumes you know things he'll tell you later, and he knows things you'll tell him later.&lt;br /&gt;&lt;br /&gt;To code this will be tricky, but here are some sample ideas:&lt;br /&gt;&lt;br /&gt;- You can't tick him off, because every time you start an argument with him, he just says, "Apology accepted" and walks away.&lt;br /&gt;- You are told to get some tissue sample from him, and when you show up he asks, "Did you bring the tongs?" before you have a chance to ask him for it. He then explains, "Look, I'm going to tell you that you can't handle this sample without tongs, so where are they?"&lt;br /&gt;- In combat, he always just randomly chucks grenades places, except it always so-happens that those places are exactly where the enemy will be soon. (Code this by making enemies by attracted to his grenades. If it so happens the enemy dies first, just chalk it up to bad aim on Lt.'s part.) On the other hand, sometimes he fires at thin air where an enemy was a few minutes ago.&lt;br /&gt;&lt;br /&gt;[ ... other members, including one who is kill-happy ... ]&lt;br /&gt;&lt;br /&gt;A central conflict is that some of your doctors believe in never harming anyone, while others believe that some people must be harmed if it saves more "good" people. You will NOT be able to make all your doctors happy -- these two doctrines are fundamentally incompatible, and no matter what you decide, one of your doctors will eventually leave your hospital in protest. You must decide where your morality lies.&lt;br /&gt;&lt;br /&gt;Part of game play will be the missions, fought as in Fallout, and the other part will be managing your base and finances, like SimHospital. This combined model worked well in the original Jagged Alliance (without base building, but with finances and a staff that needed to get paid), and X-COM: UFO DEFENSE (spelling correct).&lt;br /&gt;&lt;br /&gt;Both treating walk-ins or saving people from space will give you money with which to buy new equipment or hire new staff (nurses, etc), or upgrade your existing people (buy training for them, upgrade your robot, purchase nanobot upgrades for the cloud, etc), or buy better weapons, etc. You can dedicate more of your hospital to treating non-emergent cases if you want to play it safe and let the chronic patients pay your bills, but obviously you'll have a harder time dealing with the big emergencies that pop up as part of the plot.&lt;br /&gt;&lt;br /&gt;If you do a good job in your sector, the government may grant you certain boons -- making you an official resource, giving you more interesting missiones, etc. Another possibility is that different space services (fleets) actually "subscribe" to different rescue organizations (instead of it being sector-based), so as you do better more fleets sign up with you. This would modify the above-described universe a bit -- in this version, civilian ships would carry a (VERY expensive) beacon which is tuned to your base, and if they ran into trouble they'd hit the panic button and the beacon would teleport your crew onto their ship, hopefully to save the day. So, each fleet would have to decide ahead of time which rescue organization's beacon they want to carry, and you could bid against other rescue organizations to put your beacon in various fleets -- but if you undercut the competition too much, you'll find you're not turning a profit with all the expenses of saving people (ammo, medical supplies, teleport costs, etc).&lt;br /&gt;&lt;br /&gt;[ ... ending not decided ... ]&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/11049281-4686967194611815234?l=wilshipley.com%2Fblog' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/4686967194611815234/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=11049281&amp;postID=4686967194611815234' title='16 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/4686967194611815234'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/4686967194611815234'/><link rel='alternate' type='text/html' href='http://wilshipley.com/blog/2007/10/video-game-idea-space-911.html' title='Video Game Idea: &quot;Space 911&quot;'/><author><name>Wil Shipley</name><uri>http://www.blogger.com/profile/04429428424208378747</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='07967666282086327688'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>16</thr:total></entry><entry><id>tag:blogger.com,1999:blog-11049281.post-4482694745307531130</id><published>2007-09-27T01:55:00.001-07:00</published><updated>2007-09-27T02:13:15.886-07:00</updated><title type='text'>Go, NBC! You're SMURT!</title><content type='html'>I own the entire first season of Heroes -- bought it on iTunes. Same with Battlestar Galactica seasons 1 and 2, and The Office.&lt;br /&gt;&lt;br /&gt;Of course, I don't own Heroes season 2, because Mac users aren't allowed to download NBC's shows this season, and I don't have cable. I'm allergic to ads anyways. I've reached a point in my life where my time is just more valuable than that.&lt;br /&gt;&lt;br /&gt;I'm a honest man, so I certainly didn't download Xtorrent and do a search for "Heroes S02E01", and 20 hours later, watch the HD version of the show, for free, with no commercials, on my Mac. I could have, sure. And the show would have rocked, and I would have saved $2, and NBC would have lost that money for no good reason.&lt;br /&gt;&lt;br /&gt;You've got to grudgingly admire NBC's giant steel balls for thinking they can do the whole online thing alone, even though everyone else but iTunes has failed miserably up to now, and NBC's model is essentially just crappy broadcast TV, except over the net, which is, like, kind of pointless. Uh, guys, it turns out we consumers don't actually have a preference which WIRE our shows come from. Seriously, why wouldn't we just TiVo the show if that's what we wanted?&lt;br /&gt;&lt;br /&gt;I mean, you've got to think, "Wow, those NBC execs really are independent thinkers! Stupid, sure, but independent! They aren't Steve Jobs' toadies! No sir! Look at the way they saw away at their noses! Their faces sure won't ever mess with them again!"&lt;br /&gt;&lt;br /&gt;It's amazing how 100% of the companies that make their living distributing other people's content are run by absolute morons. Like, they fulfill no meaningful purpose in society -- these are the guys from the Hitchhiker's Guide that got sent in the first ship. I mean, you literally could put an African Grey parrot in charge of NBC and it'd make better decisions. (For one, there'd be a lot more shows about pirates, which would be awesome.) I defy anyone to think of a good decision that a record or TV or movie company has made recently. Let's sue teenagers! Let's make it impossible to enjoy our content over the web! Let's fund the seventeenth sequel to a crappy movie that barely made money! Let's have a fall season full of the same shit we've made a hundred times! Let's fire our writers and go entirely with reality shows that aren't real! Let's give outrageous amounts of money to washed-up artists and ignore the new talent that's actually selling records now! Let's cancel shows that are at all interesting and have a smart audience, because only stupid people watch TV now! Let's sue sue sue instead of create create create!&lt;br /&gt;&lt;br /&gt;Maybe Amazon will start selling Heroes for $1 an episode, and non-copy-protected? Apparently Universal WUBS Amazon, so who knows?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/11049281-4482694745307531130?l=wilshipley.com%2Fblog' alt='' /&gt;&lt;/div&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/4482694745307531130'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/4482694745307531130'/><link rel='alternate' type='text/html' href='http://wilshipley.com/blog/2007/09/go-nbc-youre-smurt.html' title='Go, NBC! You&apos;re SMURT!'/><author><name>Wil Shipley</name><uri>http://www.blogger.com/profile/04429428424208378747</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='07967666282086327688'/></author></entry><entry><id>tag:blogger.com,1999:blog-11049281.post-2999130836529163748</id><published>2007-09-21T03:28:00.001-07:00</published><updated>2007-09-26T04:06:23.616-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='humor?'/><category scheme='http://www.blogger.com/atom/ns#' term='politics'/><title type='text'>Bush &amp; Sons, General Contractors.</title><content type='html'>Well, now, Mrs. Ross, I understand you have some concerns about our latest bill, so daddy's sent me over to talk about it with you. I'll be straight with you from the start: I don't know what half of this stuff is, either, but, heck, we'll muddle through it together, OK?&lt;br /&gt;&lt;br /&gt;Yes, ok, I can see how you'd be upset that we said it'd cost $5,000 to fix your shower and now we've billed you for $5,000,000. No, I don't know where you're going to get that kind of money... uh, can you, like, mortgage your house, or something? That's a thing, isn't it? Mortgage? Funny sounding word. Mort-gage. Mortgage.&lt;br /&gt;&lt;br /&gt;What? Oh, sorry. No, you really do have to pay it, I'm sorry, Mrs. Ross. Well, no, that's fair, we didn't actually fix your shower at all. Actually, we decided to start with the toilet, which is where we've concentrated most of our efforts for the last few years or so. No, no, I understand you thinking that was a mistake, especially in light of the fact that it turns out there was nothing wrong with the toilet, and you really wanted that shower fixed. But we didn't know that, going in! We had a gut feeling the shower and toilet shared the same bad plumbing! Yes, I know they are in different bathrooms, but the bathrooms sure look a lot the same, don't they? Didn't you find that a LITTLE suspicious? It's a logical conclusion. Sometimes you just know things, despite what people tell you, and you just have to take a leap. Ok, sure, yes, a $5,000,000 leap.&lt;br /&gt;&lt;br /&gt;Yes, I suppose your grandkids &lt;i&gt;will&lt;/i&gt; be paying for this shower until they're your age. Sometimes life works that way. Actually, it's not really the shower they'll be paying for, remember -- we decided not to work on the shower. It's the toilet that we're billing you for. Ok, fair, that's not fixed, either -- we kind of took it apart and, I'll be honest, we realized we're not plumbers. We have no idea how to put it back together. Man we feel really bad about that. That there is really where a lot of your expense comes from -- I mean, if we'd just taken it apart and put it together in a month, you can see how our bill would have been a lot smaller, huh? But it's been, what, years now? I gotta tell you, we feel as terrible about this as you do. It's been really hard on the boys. Well, not the boys in accounting, but the boys actually doing the work. Well, also not our suppliers. They're pretty happy, I gotta admit.&lt;br /&gt;&lt;br /&gt;I know I threw that party for my boys and said we were "done" years ago... but that really needs to be put into context, you see. When I said "done" I meant "done with the hard part," which was convincing you to let us tear up your bathroom. I mean, we knew once we got in there wasn't going to be any finishing for years. No, in retrospect, I guess I could have been clearer about that. But that's ancient history, now. I mean, you've got a new toilet to look forward to, someday! Right, and a shower, if we get to it.&lt;br /&gt;&lt;br /&gt;And of course, there was the damage to the area around the toilet caused by old Jim Blackwater. Oh, yah, I totally agree, I'm not even gonna argue -- he is one creepy dude. I'll give you that. I'm not gonna pull yer leg, Mrs. Ross -- he's not really a contractor. No license, no nothing. He's just a drifter who really likes tools, but he was too kooky to ever get a license. But, you know, recently we've been a bit short on men, so we thought, hey, he has his own hammer, and chances are good that at least SOME of the things he'll hit with it will be nails, so, why the heck not? I mean, it's not &lt;i&gt;our&lt;/i&gt; money we're spending, so you can see our thought process, right? Look, I'm real sorry if he's been givin' your daughter the creepy eye, but to be fair you gotta admit she shows a lot of leg for a 15-year-old.&lt;br /&gt;&lt;br /&gt;You gotta unnerstand, we could have read some book on fixing toilets and just gotten the tools and put in the time and acted like them pointy-headed city wonk plumbers do. But we're from Texas, and we do things a little differently there; we shoot from the hip. We don't like them fancy college boys tellin' us they know what to do just because they got book learnin' and experience and all that. Well, ok, fair, I &lt;i&gt;did&lt;/i&gt; go to college too, but I didn't go to class, so I don't think it counts against me.&lt;br /&gt;&lt;br /&gt;But, look, Mrs. Ross, try to see the bright side: I think we're making real progress, finally. No, I mean, with the toilet situation. We haven't had any progress on the shower, no; I keep explaining that we decided to do the toilet first. Yes, ok, fine, it wasn't broken, but it &lt;i&gt;could have been&lt;/i&gt;, and it &lt;i&gt;is now&lt;/i&gt;, so I think we should just keep doing the exact same thing we've been doing until it suddenly starts working.&lt;br /&gt;&lt;br /&gt;Also, while I have you here, we've identified some problems with your sink now that the plumbing around the toilet is all dug up, and the boys are real eager to have a crack at &lt;i&gt;that&lt;/i&gt;. Heck, we're probably gonna have to hire some more apprentices! Look, don't worry about the cost, yet -- those aren't final totals.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/11049281-2999130836529163748?l=wilshipley.com%2Fblog' alt='' /&gt;&lt;/div&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/2999130836529163748'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/2999130836529163748'/><link rel='alternate' type='text/html' href='http://wilshipley.com/blog/2007/09/bush-sons-general-contractors.html' title='Bush &amp;amp; Sons, General Contractors.'/><author><name>Wil Shipley</name><uri>http://www.blogger.com/profile/04429428424208378747</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='07967666282086327688'/></author></entry><entry><id>tag:blogger.com,1999:blog-11049281.post-7411183858910346502</id><published>2007-09-19T17:30:00.001-07:00</published><updated>2007-09-24T02:26:15.140-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='mac community'/><category scheme='http://www.blogger.com/atom/ns#' term='business'/><title type='text'>iPhone &amp; iPod: contain or disengage?</title><content type='html'>Back when we had commies to worry about, someone came up with the concept of "engage and contain": eg, rather than avoid them as we'd been doing, we should trade and talk and travel there, and by doing so be able to contain their evil.&lt;br /&gt;&lt;br /&gt;Similar ideas exist today on China and Iran (And, honestly, people -- do we really worry about being attacked by Iran? Really? Is this even on our RADAR?) Google is infamous recently for installing government-censored Google in China, with what I think were the purest of intentions -- the idea that more knowledge naturally makes the country more democratic.&lt;br /&gt;&lt;br /&gt;But even Google executives have recently said they think they've made a mistake, because by getting too close to the Chinese government, they've had to make compromise after compromise, until finally Google finds themselves an accomplice to evil instead of an adversary to it.&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;So it is with iTunes. Apple has engaged two of the most cock-thirsty and money-grubbing conglomerates in the United States -- the movie and record industries -- in what we all wanted to believe was an attempt to engage and contain them. And, initially, we all agreed Apple was doing good: they had, for the first time, made legal downloads more compelling than stealing music. For a single data point, I've personally bought 915 songs from the iTunes music store, and hundreds of TV episodes and dozens of movies. I own six iPods and have bought 18 iPhones to give away.&lt;br /&gt;&lt;br /&gt;And we all took heart when Steve published that letter saying how much he hated DRM, and how he'd drop it if the labels would, and even if the rumors are correct and EMI was already planning to drop DRM and Steve just rushed in and took credit, it was still a bold stance for him to take; a challenge to the rest of the industry. And I immediately upgraded all the tunes I could to iTunes Plus, and bought a bunch more albums. And it was good.&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;But recently, well... the generous view would be that Apple's screwing up, and the non-generous view would be that they are just plain getting greedy.&lt;br /&gt;&lt;br /&gt;No, I'm not talking about the iPhone price reduction. Honestly, I was happy to see the price go down, even though I could have personally saved $3,000 if I'd waited to buy the 15 phone I bought before the reduction. I mean, c'est la vie, it's technology, baby.&lt;br /&gt;&lt;br /&gt;But why is the iPhone locked to a single carrier, so I can't travel internationally with it? There's really only one viable reason: Apple wanted a share of the carrier's profits, which meant giving AT&amp;T an exclusive deal. Which meant, we get screwed so Apple can make more money. It's that simple.&lt;br /&gt;&lt;br /&gt;And the iPhone is a closed system, like the iPods before it, so third parties can only develop software for it if they are EXTREMELY close to Apple. This is an incredibly frightening trend. As Apple gets more and more of its revenue from non-Mac devices, they are also getting more and more of their revenue from devices that simply exclude third parties.&lt;br /&gt;&lt;br /&gt;I know Steve Jobs; he's actually amazingly like my old business partner Mike Matas. They both love closed systems, for a simple reason -- they both know they're smarter than anyone else on the planet, and they don't need anyone else mucking up their systems. Steve would rather have no third parties for Mac OS X if he could get away with it -- Apple, of course, would do a much better job on &lt;i&gt;anything&lt;/i&gt;, but since customers &lt;i&gt;insist&lt;/i&gt; on Photoshop and Office and other apps, he puts up with them. (Well, except, now Apple has their own office suite.) Steve knows that on a computer, having a broad spectrum of apps is more important that having them all be Apple-perfect.&lt;br /&gt;&lt;br /&gt;But on iPods, Airports, Apple TVs, and now iPhones, Apple wants every app perfect. Which is nice, &lt;i&gt;in theory&lt;/i&gt;. In practice, it means innovation only happens at Apple's pace. The marketplace of ideas is much smaller, and the devices are much poorer because of it. (Example: Why can't I stream music from my iPhone or iPod touch to my Airport Express?)&lt;br /&gt;&lt;br /&gt;There &lt;i&gt;are&lt;/i&gt; some third parties making money from the iPod -- hardware accessory makers. But even then, Apple is trying to charge them a "Made for iPod" sticker tax... for adding no value. And since Apple controls the stores in which iPods are sold, they have a pretty effective stick to use against those who don't comply - you won't be where the players are. But with the latest iPods Apple's gone a step further, and disabled some docking stations that don't have a special chip in them provided by Apple; forcing customers to use only Apple-approved accessories. Apple's emulating the most pernicious qualities of Nintendo and the Microsoft XBox -- you pay us a tax or you don't work with our systems.&lt;br /&gt;&lt;br /&gt;But Apple's "approval" just comes from Apple getting a cut. It's a measure of greed, not quality. We're not talking about THX-certification here, we're talking about extortion. This kind of lock-in seems very appealing for the company doing the locking early on, but it always, ALWAYS ends up biting the company in the butt. Ask IBM with their ubiquitous 970 servers and their extortionist service contracts. Oh, wait, those don't exist any more.&lt;br /&gt;&lt;br /&gt;Consumers suffer from this. We suffer from increased prices and decreased competition and innovation. We suffer so Apple can make a few more bucks, when Apple is clearly not hurting for money. The core of Apple users has supported Apple for years -- we were there when Apple was hurting, we stuck with it, we nursed her back to health. It's our money she has now, and she's turning on us now that she's rich off it.&lt;br /&gt;&lt;br /&gt;Then we come to ringtones. Every phone I've owned in the last ten years has allowed to make my own ringtones. I could upload MP3s all I wanted. Many had little tune editors built into the phone.&lt;br /&gt;&lt;br /&gt;But since Apple is so close to the record companies, and they are already so grumpy with Apple, Apple did a deal that benefits record companies and Apple. Not artists, certainly not consumers. In order to use a 15-second snippet of some random song, I now must buy it not once, but TWICE. The amazing thing is that I must buy it THREE times if I own the song on CD -- I have to buy a DRM'ed version from the Apple Store, then buy the the ringtone, on TOP of the CD I already bought.&lt;br /&gt;&lt;br /&gt;Oh, but wait, most artists haven't given permission for their songs to be used as ringtones. The vast majority of my collection simply can't be put on my iPhone as a ringtone. I could, if I wanted, manually press play on those songs whenever I see a friend calling, but that single "if" statement it'd require for the phone to do it -- well, that's simply Not Allowed.&lt;br /&gt;&lt;br /&gt;Not that, uh, we have to pay attention to what the record companies think is Not Allowed, because we have &lt;i&gt;already licensed&lt;/i&gt; the song for playback on any device if we bought a CD -- we are allowed to play it on our iPhone already. Just not in response to someone calling us. The record companies have MADE UP some new, retroactive copyright and Apple is enforcing it for them. The result is, a million customers don't get to do something cool with their iPhones.&lt;br /&gt;&lt;br /&gt;Because of greed.&lt;br /&gt;&lt;br /&gt;Honestly, I can see Apple saying, "Well, you see, the record companies would have been upset with us if we hadn't charged anything for ringtones." Yah, well, that's the price you get for engaging. The price for owning the distribution of the content and the hardware and the software is that you end up making compromises in the hardware and software in order to protect the content.&lt;br /&gt;&lt;br /&gt;These are EXACTLY the compromises Sony has been making for years -- and because Sony's music and movie arms have been telling the Sony hardware arm to never do anything new or interesting without building in a ton of customer-unfriendly restrictions, Sony is now completely in the toilet. They have gone from an incredibly respected brand to a complete joke. Every time they introduce some new, crippled standard the industry kind of looks away in embarrassment, like Sony is the oafish guy at the party who is parked in front of the meatballs tray eating directly from the dish.&lt;br /&gt;&lt;br /&gt;Now we see that iPod owners who upgrade to a newer iPod must re-buy the games they've already bought, because the new iPods are incompatible with the old. No credit given for having already bought an identical game. Imagine upgrading to a new computer, and having to buy a brand new copy of Windows Vista for it... Oh, wait, Microsoft does that, don't they? MICROSOFT does.&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;What should Steve do? Well, for starters, give up on trying to control everything. It's only going to keep hurting Apple, more and more, to control content and hardware and software. It's going to make them into the kind of mega-monopoly that we always, ALWAYS end up hating. Absolute power corrupts absolutely. 100% of the time.&lt;br /&gt;&lt;br /&gt;Apple should license FairPlay, or allow iPods to play PlaysForSure (ha! I love that doublespeak) music. Either one. Basically, Apple should allow other music stores to sell DRM'ed music that works on iPods and iPhones.&lt;br /&gt;&lt;br /&gt;Why? It's simple -- then Apple could tell record companies "go fug yourself" if they don't like Apple's terms, but Apple would still have a full range of music to play on its iPods. Remember, Apple makes all its money selling the hardware, not the songs. All Apple needs to do is to make sure there is a broad range of content available for iPods, it doesn't have to sell &lt;i&gt;all&lt;/i&gt; that content itself.&lt;br /&gt;&lt;br /&gt;And, in fact, it hurts Apple to sell all the content itself, because it makes Apple a focus for battles between the record industry and consumers. If there were a range of stores selling iPod-compatible music, with a range of different DRM rights, then the market could decide what terms it liked best.&lt;br /&gt;&lt;br /&gt;The iTunes store could be the white knight -- it would only sign deals with record companies willing to "give" consumers the same rights they've had for years with CDs; eg, we can do whatever we want with our music as long as we don't broadcast it or give it to others. Other music stores could sell restrictive DRM'ed music, and, well, if the record companies are right, people would go to those other stores, and we consumers would all get what we deserve.&lt;br /&gt;&lt;br /&gt;But if I'm right, then those other stores would be soundly ignored, and the record companies would come crawling back to Apple with their tails between their collective legs (where their balls should be, but aren't) and agree to reasonable terms.&lt;br /&gt;&lt;br /&gt;Sure, we've seen some of this with Apple's negotiations with NBC, but unfortunately this one is all-or-nothing for Apple, because there's no &lt;em&gt;alternate&lt;/em&gt; method for NBC's content to get onto iPods. Apple &lt;em&gt;needs&lt;/em&gt; to be able to say, "Look, NBC, you want to be dumb-asses and try to sell people crap they don't want, fine -- we're still going to sell iPods that'll play your programs, we just won't sell your programs on the nicest internet store in the world. Your loss, suckers, call us when you change your mind."&lt;br /&gt;&lt;br /&gt;Second, Apple should announce that it's going to write frameworks so third parties can write applications for iPods and iPhones. No, it won't be easy. But, seriously, there's no excuse. I mean, with the iPhone they could hide behind AT&amp;T wanting assurances people won't use their phones off-network, or behind consumers wanting their iPhones to never crash. Which are both reasonable points, I admit. And, for the record, I've never written a line of code for the iPhone, although one of my employees has (in his spare time). I don't like to screw with undocumented APIs, life's too short.&lt;br /&gt;&lt;br /&gt;But with the iPod Touch, what's Apple's excuse for locking up the platform? Why can't I write programs for this device? Who might it hurt? Why is Steve announcing that he's playing cat-and-mouse with developers who intend to do so? Is Apple so far removed from its customers that even when the latter overwhelming votes for extending a device (by downloading iPhone programs in the hundreds of thousands), Apple's response is, "No, you can't do that. We know what you want, you don't. You want AJAX apps, you just don't know it yet."&lt;br /&gt;&lt;br /&gt;That sure reminds me of the old, crappy Apple. The one that almost went bankrupt because of its hubris.&lt;br /&gt;&lt;br /&gt;I don't write programs for Apple because I worship Apple. I write programs for them because they have the best development environment. But I've always said that I will move from the platform the day Apple starts acting like a monopoly -- trying to make money by using its marketing position to extort money from users, instead of innovating so quickly that users willing throw money at Apple.&lt;br /&gt;&lt;br /&gt;Sure, Apple's still doing a ton of innovating. I love Leopard. I love iPhone (x19). I love my iPods (x6). And I love the engineers at Apple and all my friends throughout the company.&lt;br /&gt;&lt;br /&gt;But Apple has to always remember that simply making money CANNOT be its point of existence. The point of any company should be to make customers &lt;i&gt;want&lt;/i&gt; to give it money, NOT to get money from customers. It's a subtle distinction that is the difference between good and evil.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/11049281-7411183858910346502?l=wilshipley.com%2Fblog' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/7411183858910346502/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=11049281&amp;postID=7411183858910346502' title='52 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/7411183858910346502'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/11049281/posts/default/7411183858910346502'/><link rel='alternate' type='text/html' href='http://wilshipley.com/blog/2007/09/iphone-ipod-contain-or-disengage.html' title='iPhone &amp;amp; iPod: contain or disengage?'/><author><name>Wil Shipley</name><uri>http://www.blogger.com/profile/04429428424208378747</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='07967666282086327688'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>52</thr:total></entry></feed>