Pro Tip: Data Types Matter
I made a stupid mistake recently, which cost me more than a few hours of time to figure out. But I learned a couple things: Xcode will let you dig as deep a hole as you like, despite its “assistance”, and no amount of experience is a substitute for taking your time, especially when writing code.
I was implementing a custom view recently, with an embedded UITableView. No problem. At this point, we can all probably stub out the required UITableViewDelegate and UITableViewDataSource methods with our eyes closed. Or can we? What’s wrong with this declaration:
[code lang=”objc”]
– (CGFloat)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
…
[/code]
At first blush, nothing. I think we can all agree that we’d remember that this is a required method to implement when working with tables. At least, we’d all certainly remember we need to implement a method named numberOfRowsinSection.
What is wrong here, however, is the return type. There are a lot of places in various protocols where the return type is a CGFloat. So, why not here? In fact, when I implemented this method, I didn’t cut-and-paste the signature from the docs, or even look at the docs. I just started typing, and despite Xcode’s suggestion, which included the correct return type, I pressed on without really looking at the pop-up.
I implemented the method and moved on.
When I began to test my code, however, things got weird. My view controller worked great! The embedded table view worked exactly as I wanted. The first time. If I left my view controller and tried to return to it a second time, the app would crash. And it was one of those nasty what-just-happened-and-how-the-heck-do-I-debug-this?! crashes.
I really spent a lot of time researching, debugging line by line, re-testing, re-jiggering, you name it!
As almost a last ditch effort, I carefully reviewed the implementations of all my UITableView delegate and data source methods. Sure enough, I found I had implemented the above method with the wrong return value. And when I changed it to correctly return an NSInteger instead of a CGFloat, all was right with the world.
I chalk up this mistake, honestly, to haste. I was in a hurry. I knew what I needed to do. Clearly I didn’t know enough to look twice. 🙂 This all happened on one of my personal projects, but it’s a good lesson and reminder that haste makes waste, even in software development.
Type a dash and then tableView… And Xcode will bring up options. That is, don’t type the return value type.
Here’s a pro tip from me:
If you start writing a predefined method header like in your example, omit the type. I.e., just type:
-tableView
That’ll be detected by Xcode’s code completion assistant, and if you then choose your function from the menu, it’ll add the right return type for you as well.
that’s interesting! I usually disable the autocomplete. Doesn’t it gets caught by the Analyzer? I would try it when I get back to my machine.
Sid, Why the heck would you enable auto-completion?!
It would be great for Xcode or the ObjC runtime to catch errors like this. One approach we use is in our frameworks, https://github.com/omnigroup/OmniGroup/blob/master/Frameworks/OmniBase/OBRuntimeCheck.m
With this in place, you get a runtime warning like:
2012-11-29 08:41:54.267 OmniOutliner-iPad[50968:c07] Method tableView:numberOfRowsInSection: has conflicting type signatures between class and its adopted protocol:
signature f16@0:4@8i12 for class ColumnFormatterInspectorPane has imp -[ColumnFormatterInspectorPane tableView:numberOfRowsInSection:] at 0x81ee0 in OmniOutliner-iPad
signature i16@0:4@8i12 for protocol UITableViewDataSource
Comically, we had to add a mode to squelch warnings from within the system frameworks.