August 2012

Today we saw our first Fuck Glassboard tweet. (I think. I could have missed one before.)

I don’t handle support these days, but I did it for years, and I have a few rules I follow in this situation.

Be kind! The answer is never to strike back. (In fact, your product’s best evangelists are sometimes people who cursed at it at first.)

Don’t try to be funny. Humor and personality are great most of the time — but when someone is angry, they’ll take it as flip or condescending.

Use the person’s name. (Actually, that goes all the time. Don’t underestimate the importance of this.)

Tell them you want to help, and give them an easy way to reach you directly. Even if it means putting your email address on the web. (Which just demonstrates that you care more about helping them than you care about avoiding spam — because you do care more about helping them.)

Then, of course, help them.

This doesn’t always magically work — they’re not necessarily going to suddenly transform into a fan. But you have to do it anyway.

The hard part is situations like this specific one — her follow-up tweet says:

It’s not that I’m “having troubles,” it’s that I hate using a service that requires me to use their proprietary app/website.

No amount of help is going to fix that problem for her today, right now. Sometimes you have to know when to stop — a person wants to make a point, and then they’re finished. You take the point and move on.

We chose HTML as the first format to support. (We might offer choices in the future. And might not, since HTML is universal.)

Nick Harris did a good job on this. As I recall he had to fight some libraries to make them work with Unicode. (Which is never a fun time, and it makes me so glad to live every day in the NSString world.)

The Three Phases of Startup

A table-based, data-driven app like Glassboard goes through three states:

Loading. System loads the app. It displays Default.png, and there’s no interactivity.

Loaded-but-useless. App is loaded. UI is displayed. But there’s no data yet — the data still has to be fetched.

Loaded-and-useful. Data has been fetched and displayed. The app is ready to use at this point.

Users don’t really distinguish between #1 and #2. If the first step is fast, but the second step is slow (or vice versa), your app won’t get a pass: both steps need to be fast, because the user is waiting for #3, for UI and data.

How I Made Step #2 Fast

(If you’re a veteran Cocoa developer, you already know how I did it, and you can skip reading this article.)

I assume you know how to make step #1 fast. (In general: run the minimum amount of code, avoid memory allocation, don’t block the main thread ever. If your main screen is just a table, don’t bother with a xib. Profile to figure out what’s really slow.)

After I optimized app-loading, I wanted to get the data loaded so fast that there was no delay between loading and loaded-and-useful. I wanted to wipe out step #2.

There was no way I could optimize the SQLite fetches to be fast enough to make that happen. (It needs to fetch messages and comments and their related boards and people.)

I needed a way to cheat.

I reasoned this way: the app would show the data that was current when you last quit the app. Yes, there would have been changes on the server since then — but those would have to be downloaded regardless.

So whether I fetched from the database or cheated in some way, the end result would be the same: the app would show the data from when you last quit. (Until downloads complete with new and updated data.)

I remembered a technique I had used in some old version of NetNewsWire for iPhone, which was developed on much slower hardware.

The NetNewsWire Solution

NetNewsWire displayed an outline of feeds with unread items. Creating that outline was very, very slow. It could take several seconds on the iPhones of that era.

There was no way I could let startup be delayed several seconds — that would have been monstrously bad. So what I did was cache just enough info on disk to be able to rebuild the outline without hitting the database.

I don’t remember if I used NSCoding or a custom plist-based serialization — but it’s the same concept. I saved the outline on disk periodically, and at startup read that file to create the outline.

The Glassboard Solution

I did the same thing here: I cached the data on disk. The messages displayed in that table are stored in a single array in the app, and I used NSKeyedArchiver to serialize it and NSKeyedUnarchiver to de-serialize it. I made the serialized versions of the objects also contain just enough info about related people and boards to create those as well.

This meant adopting the NSCoding protocol for several of my classes (statuses, boards, and people). (Since I’m not using Core Data — for good reasons worth writing up, and different from last time — this was easy.)

When serializing, creating the NSData object to write to disk is separate from actually writing the data. The app creates the NSData on the main thread (via -[NSKeyedArchiver archivedDataWithRootObject:] — which is so fast you’d never notice. And then it writes the data to disk in the background, inside a dispatch_async block.

When de-serializing at startup, the NSData is read from disk and the objects are instantiated via -[NSKeyedUnarchiver unarchiveObjectWithFile:].

I tried two different times:

As part of startup.

Right after startup.

Right-after-startup meant there was a perceptible, though very small, delay between app-loading and loaded-with-UI-and-data. The whole point of all this was to get rid of that delay.

But I was worried that de-serializing as part of startup would be too slow — I might get rid of that delay but at the cost of longer app-loading time.

I tried it.

And it was so fast it had no perceptible effect on app-loading time, and it got rid of that delay between app-loading and loaded-with-UI-and-data.

I got what I wanted.

Seeing it in Action

If you have Glassboard 2.2, kill the app (double-tap the home button, tap-and-hold on the app till it shimmies, tap the - button) and re-launch it.

You’ll see a couple seconds of the Default.png file (which is unavoidable), and then the actual UI and the data will load at the same time.

Obvious To-Do Item

But note what I still have to do — the avatars load after the messages. I don’t mind if they’re not ready immediately, but they should appear much more quickly than they do.

I don’t know yet how I’m going to solve this. But it has an interesting component to it: I only need those avatars that are visible right away. That simplifies the problem.

I’m not sure that NSCoding is the way to go here, since image data can be large. (Even small images can be surprisingly large, since we’re on retina displays.)

But you can bet two things: 1) there’s a solution, and 2) I’ll be obsessed with the problem until I figure it out.

People would point out correctly that the app is free and we’re not asking for any money. How could we do that?

Well, our plan has been to introduce premium features, things that some people will want to pay for.

Today we did that.

The app is still free. To be useful to you, you need other people to use it with you, and so the app is free.

But we’ve added the first round of premium features — board export, board transfer, bookmarking, more storage, unlimited board creation — for people who need more. If you have a premium account, the app will notice and the new features will appear. (In iPhone, Android, and on the web.)

Update 7:40 pm: Underneath is a JSON feed. Which makes it super-easy to re-purpose and display however you want.

If you started today, you could write an iOS or Mac app that displayed a river of news in a day. It wouldn’t be pretty, and it might not perform well, with just a day’s work, but you could easily write the code to:

Just as literature fans grumble and gripe about the success of 50 Shades of Grey, App.net backers are implicitly kicking out at Justin Bieber trending and the preponderance of painful hashtag games. But they can’t admit their elitism because that’s one of the 21st century’s bad words.

The App.net pitch says, “We believe that advertising-supported social services are so consistently and inextricably at odds with the interests of users and developers that something must be done.”

I backed the project, and I agree.

But if you asked me why I want a Twitter alternative to succeed, I don’t think I’d express it so well. I’d say things like “because I fucking hate ads” and “because I fucking hate celebrities” and “because I fucking hate trending topics.”

So App.net won’t have ads — but I also hope it’s a long time before the Biebers, Kuchers, and their idiocratic, smeghead fans discover the service.

I wonder, just a little bit, if my attitude makes me out to be a jerk. I think it probably does — and I think, in this case, I don’t care.