Dec 2012

A Quick Cheat I’ve Used

Given a UITableViewCell, you can get its indexPath and, thus, the model object it represents.

But you can also cheat at it. I’ve done this in the past, and I’m not sure how I feel about it.

#import <objc/runtime.h>

The above line should be scary. Just because you can build planets out of proto-matter doesn’t mean that you should.

static const char *BSAssociatedModelObjectKey = "BSAssociatedModelObject";

In your view controller, in tableView:cellForRowAtIndexPath:, after getting the cell and looking up the model object:

objc_setAssociatedObject(cell, BSAssociatedModelObjectKey, modelObject, OBJC_ASSOCIATION_RETAIN);

Then, later, when you have a cell and needs its model object:

modelObject = objc_getAssociatedObject(cell, BSAssociatedModelObjectKey);

This way the model object is associated with the UITableViewCell — but the UITableViewCell itself doesn’t know that or know how to look at it. It’s just a convenience for the view controller.

I’m not sure that the convenience is worth it — it’s not like it’s hard to go from a cell to its model object. And any time you’re getting the runtime involved is weird. Red flags go up — with your name on them, embroidered with radium thread.

But. I can’t help but like it anyway, even though I don’t actually use associated objects in this way. (Anymore.)

If this is all new to you, see Ole Begemann’s Faking instance variables in Objective-C categories with Associative References.

UITableViewCell Is Not a Controller

In a previous post I wrote that passing model objects to a UITableViewCell subclass is a bad practice.

Some people asked me why, so I’ll explain.

What I mean by model object

By model object I don’t mean NSStrings, numbers, and so on. I mean the thing that a table row represents, which is typically an object with a bunch of properties.

If it’s a song, for instance, it would have properties such as artist, title, duration, and the number of times played.

I’m not against passing those values to the cell: I’m against passing the song object to the cell.

It goes against MVC

If a UITableViewCell knows about a model object, then the UITableViewCell is responsible for taking data from the model object and displaying it. But that’s not typically the way we work. Cocoa is an MVC-based system, which means that controllers come between models and views. A UITableViewCell is a view, not a controller.

In the case of table views, there will be an object, typically a UIViewController or UITableViewController, that implements (usually both) UITableViewDataSource and UITableViewDelegate protocols. That object is the controller, and it has the job of coming between the model and the UITableViewCell.

Aren’t rules meant to be broken?

MVC is a useful design pattern — it’s not a straightjacket we’ve all agreed to wear. There are times when an expedient short-circuit is easier, more clear, and more maintainable.

Right?

Here’s President Obama quoted in Obama’s Way by Michael Lewis:

“You’ll see I wear only gray or blue suits,” he said. “I’m trying to pare down decisions. I don’t want to make decisions about what I’m eating or wearing. Because I have too many other decisions to make.”

As a programmer you make decisions all day (and maybe all night) long. When a choice between MVC and anything else comes up, choose MVC. Save yourself from decision fatigue — because you want to be at your best for all the hard choices.

When I was a less-experienced programmer I would have disagreed with the above. I would have replied that there’s no excuse for not using your brain.

But over the years I’ve learned that one of the keys to working intelligently is recognizing when you’ve encountered a settled matter, and being wise enough to avoid wasting any decision energy when those come up.

Still, though — yes, there may be times when doing something other than strict MVC is the best thing. Mmmmmmaybe.

But here are some of the issues you could run into if you do think you can get away with not-MVC.

Asynchronous images

Imagine the common case where a UITableViewCell needs an image, and the image is loaded asynchronously from the web. It comes from the web and is probably cached on disk or in a database and also cached in-memory.

Spot the problem with this code:

[self.imageFetcher fetchImage:someImageID completionBlock:^(UIImage *fetchedImage) {
  self.imageView.image = fetchedImage;
}];

Since fetchImage:completionBlock: is asynchronous, you can’t know when the block will be called. The fetched image may no longer be the appropriate image to show in the view — the cell may have been recycled.

So you could add something that checks to see if that’s still the correct image. But you’re starting to stand on your head at that point. And you’ll also note that possibly several UITableViewCells have asked for the same image to get fetched, possibly causing a performance hit.

It’s much better to do this work in the controller class — it can ask for each needed image exactly once, and it can create an in-memory cache that disappears when the controller is dealloced.

There are other ways you might design this — but I don’t know of a better way to handle it than in the view controller.

UITableViewCell re-use

You may be convinced that you’ll never, ever need to use your UITableViewCell with anything other than that particular model object.

But consider some cases where you might:

  1. In a UI walk-through or help system you want to show a UITableViewCell with example data. (Okay, I know — don’t do UI walk-throughs. Unless you’re getting paid to do it.)

  2. In the theming setting screen, you may want a UITableViewCell with example data. (Okay, you tell me you’ll never do theming. But what if Loren Brichter releases an app with theming, and now you think it’s cool and you want to do it too?)

  3. You’re writing something like a Twitter app, and you want to display the tweet-being-uploaded in the table — but your model objects are NSManagedObjects, and the tweet-being-uploaded can’t be turned into an NSManagedObject until you get the response back from the server with its uniqueID and other server-generated properties.

  4. You’re writing something like iTunes, and you go from having local-songs-only to local songs and songs-in-the-cloud — and they’re not the same model object. They may be similar, of course, but they’re not the same. Do you create a bunch of logic in the UITableViewCell to handle the differences?

One possibility leaps to mind: use protocols. Have the UITableViewCell take objects that conform to that protocol. That would solve all of the above.

Protocols are awesome. But as soon as you make that protocol you also have to make at least one other object which implements that protocol. At that point you are multiplying entities — and you’re standing on your head when you could have just had the view controller do its job as a controller.

Updating via the web

Last scenario. You’re writing an app where the data in the app can change outside of your app — it pulls data from the web, and your model objects update. (Maybe even just because the app syncs with other clients.)

How do the UITableViewCells get updated?

You could have each cell do key-value observing — but then you’re setting up and tearing these down frequently, at a cost to performance. (And you’ll probably have to implement prepareForReuse, which I consider a code smell.)

Or you could have it register for more-generic NSNotifications, but then each cell will be checking to see if the notification applies or not (also smelly and inefficient).

The best way to handle this is obvious: the view controller should get the change notifications and update the cells that need updating.

What I do

Enough scenarios. Now for practical stuff.

I use the same idea for every table view.

  1. The view controller implements tableView:cellForRowAtIndexPath:, which calls a method like updateCell:atIndexPath:.

  2. The view controller has registered for various types of notifications, and when a notification arrives it looks at the visible cells and determines which, if any, need updating. Then it calls updateCell:atIndexPath: for each cell that needs it.

  3. The updateCell:atIndexPath: method is where model object properties get passed to the cell, often transformed.

The key here is, obviously, the updateCell:atIndexPath: method. By having a single method that passes values to the cell, I have one place that translates a model object to the values the cell needs. And it can get called both from tableView:cellForRowAtIndexPath: and from any notification handler.

Another key is to use UITableView methods cellForRowAtIndexPath:, visibleCells, and indexPathsForVisibleRows so you can translate between model objects and visible rows (and vice versa).

If you haven’t done it before you’ll have to figure it out. But it’s pretty simple — and, once written, you’ll find it easy to use every time after that.

Well

Still, there are other ways you could do this. You could keep passing model objects to UITableViewCells and find good designs that deal with everything I’ve mentioned above.

But why? MVC works. We already know it works.

Reader Tips

NSHipster published a nice set of reader submissions. My favorite is the reminder about Xcode snippets — I keep forgetting that feature exists. (Though I do wish we also had the ability to write scripts. I miss that feature from Xcode 3.)

Coders in the Hands of an Angry God

Ash Furrow in Seven Deadly Sins of Modern Objective-C makes good points. I especially like his reminder to use NSInteger and friends:

Yeah, that’s right: every time I need a integer in Objective-C, I use NSInteger. If I need it to be unsigned, I use NSUInteger. Every. Time. Same with float and CGFloat. That takes care of the 32/64-bit conversion for me.

The one of his sins that I’m guilty of is not using automated tests. Ash writes that it’s hard, and it is. We don’t have great tools for this — but that’s no excuse. (And enthusiasm for testing should encourage tool-makers to create better tools.)

A lot of things are difficult (though not impossible) to write tests for: asynchronous code; anything that relies on database state on a server; user interface. (Is that animation smooth or jerky? How do you test that?)

Yesterday I fixed a bug in my code where it was doing database access on the main thread. (One of my rules is that database access on the main thread is never allowed.) I could have written a unit test for this — but it’s more likely I would have just tested that it fetched from the database correctly. It would have passed.

Instead, I should have used NSAssert. Something like this: NSAssert(![NSThread isMainThread], @"shouldn't be in main thread");.

Using NSAssert is easy. It also serves as a form of documentation.

While I don’t disagree about unit testing, I suggest that using NSAssert more often would be a great first step. I myself don’t use it often enough.

I’d also add a bunch of other sins. They may not be deadly, but they’re sins:

  1. Not turning on Hosey-level warnings.

  2. Not treating warnings as errors.

  3. Not fixing every single static analyzer issue.

  4. Not downloading crash logs from iTunes Connect.

  5. Putting things in your .h file that shouldn’t be public, that should be in a class extension in the .m file.

  6. Using short variable names like img and btn.

  7. Not using #pragma mark.

  8. Not understanding the concept of designated initializers.

  9. Using tap gesture recognizers when you need a button. (Think of accessibility.)

  10. Not de-queuing table cells.

  11. Calling viewDidLoad from your own code.

  12. Passing model objects to a UITableViewCell subclass.

  13. Not using conditional GET for web resources that might not change.

  14. Not testing with a clean install.

  15. Not using curly quotes — “” and ‘’ — in text that users see, including error messages.

  16. Breezy and unnecessary comments like //nil it out, kemo sabe.

  17. Not using NSLocalizedString.

  18. Using false and true instead of YES and NO.

  19. Not testing performance with Instruments.

  20. Not analyzing memory use and looking for memory leaks with Instruments.

New York Times Super-Brief Glassboard Mention

Lauren Myracle, Calling It As She Sees It:

On the parenting continuum, Ms. Myracle hits a midpoint between laid back and nosy. She wants to know what her kids are up to (in a they’d-better-not-be-sniffing-glue way). But she also wants to be the first to know their latest crush, if they’ve set up a Glassboard (a private social network)…

Of course we don’t know who uses Glassboard or how they use it (except for the people who tell us). By design.

UIRemoteViewController

Somehow I missed Ole Begemann’s well-done research into remote view controllers when he posted it in October. Fascinating and tantalizing.

OmniPresence

Omni’s plans for 2013 include some very cool things — including OmniPresence, a document syncing service.

What a perfect name.

VoiceOver On

Greg Raiz explains how to get started with VoiceOver and lists basic, intermediate, and advanced gestures.

Side note: it’s funny how the American voice pronounces Tweetbot and Netbot.

(Via Hal Mueller.)

Nick A. Looks at Simple

Secret weapon Nick Arnott digs into the security of Simple. According to Nick, it’s vulnerable to a man-in-the-middle attack, even though it uses SSL.

I love the tagline for Nick’s site: “Breaking your iOS apps.” If you’re an iOS developer, you should subscribe to his feed.

Rebuilding

Anil Dash writes about rebuilding the web we lost. Step one is “Take responsibility and accept blame.”

This is a follow-up to his post The Web We Lost, which you should read first if you haven’t already.

iDeveloper TV Christmas Special

Scotty and John Fox were nice to include an NSBabble of other developer-podcasters — me, Saul Mora, Guy English, and Manton Reece — in the latest episode: The Christmas Special.

Thrill to the dulcet tones of Saul Mora! Be amazed by the power of Manton’s explosive insights! Try to remain sane as Guy proves just how awesome he is!

Bye 5

Collin Donnell on dropping iOS 5:

The potential negatives of dropping support were that I’d have a vocal group of pissed off iOS 5 users, or that sales would go down. Thankfully, neither of these happened. I’ve received no complaints, and sales have been better for this version.

Interview with Gus

MacStories interviews Gus Mueller. I like Gus’s story of how Acorn got started:

As for why I created Acorn in the first place – it’s got a bit of an interesting history. It originally started out as FlySketch 2.0 (FlySketch is another app of mine, meant for taking screenshots + marking them up), but the update got a bit out of control and I ended up making a new product out of it. I originally created FlySketch because I wanted to see if I could force myself to draw more, and writing an app for that seemed like a good way to do that. Acorn continues that tradition.

Here’s how you know you’re a software developer: if the answer to any question — like how could I force myself to draw more? — is write an app, and then you write it and ship it, then you’re a software developer.

I use and enjoy both Acorn and VoodooPad — but the bit of Gus code I use the most is his open source SQLite wrapper FMDB. It’s in everything I’ve shipped for years.

On Human Technology

Brian David Johnson: Being More Human:

But as we near 2020, something different will happen. When computational power approaches zero, we will be able to turn anything into a computer. We can put computer intelligence into a water glass or your shirt or even your body. We no longer will ask ourselves: Can we do it? We will ask ourselves: What do we want to do?

I’m trying not to imagine a teaspoon of miniscule computer chips dissolved in my coffee.

Sandvoxing

Mike Abdullah writes about sandboxing Sandvox — the edge cases they ran into and how they solved them.

Identical Cousins 7

Cousin Michael and I talk about the successful launch of Fantastical for iPhone in the latest Identical Cousins.

PS The podcast is in iTunes now. You can subscribe from there or rate it (or both).

Archive