An incredibly well named domain. Go there and check it out:
Category: Objective C
Another creator pattern for clusters
This is about Cocoa, and in particular about class clusters. The problem I wanted to solve was having a class cluster with easily extendable hierarchy without too much interdependency. In my case, I want to create a number of different UITableViewCell descendants, depending on the particular data element the cell should handle. If the data element has a field “string value”, then a UITableViewCell with a text field for such a string value should be created. If the data element has a field “check” representing a yes/no answer, then a UITableViewCell with a yes/no functionality widget should be created instead, and so on. In total, I have less than ten different kinds of UITableViewCell derived classes, but they could become more at any time.
Don’t do parts.parts
I just found another weirdness in Apple’s Objective C. If you have the same name for several components of an object path, the runtime starts stuttering and behaving very badly. Intermittently, you can’t scroll, or only extremely slowly, everything turns into molasses. No errors, mind, just mindbogglingly slow.
This stuttering and slowness seems to affect the simulator, mainly, not so much if you run code on your device (in my case the iPad). Also, if you run under instruments, things work better (Murphy strikes again), except I had it stutter under “Allocations” but not under other instruments.
I had these repeat names in two places in my code and resolving those two seems to have fixed the stuttering entirely. To illustrate what I mean, see this code snippet:
for (IDRBase *idr in self.parts.parts) { if ([idr isSuperCell]) break; CGRect newFrame = myCGRectDown(hugeFrame, verticalOffset); UIView *view = [idr getAsView:newFrame]; if (view) { [returnView addSubview:view]; verticalOffset += view.frame.size.height; } }
(Yes, it’s the same snippet as in the previous example with nil or not nil. Coincidence. My code does indeed consist of more than just this.)
In the very first line you see a property referred to as “self.parts.parts”. That is what screws things up. Even though the two names “parts” refer to entirely different things, and even though the code actually delivers the values it should, this repetition of identical literal names confuses the crap out of the runtime, it seems. The solution is to not name these two things the same, of course. In this case, I changed the innermost “parts” to “aParts”, resulting in “self.parts.aParts”, and hey presto, no stuttering no more.
Is it nil or isn’t it?
A bit of Objective-C weirdness I don’t quite get. The weirdness occurs if “view” in the code below is nil, that is if [idr getAsView:newFrame] returns nil. You’d expect the verticalOffset not to be incremented, but it is. Even though view is nil, view.frame.size.height still evaluates to “21” (in this case) and verticalOffset gets incremented. (The first returned view was 21 high, the second returned view was nil in the debug run I’m referring to.)
- (UIView *)getAsView:(CGRect)theFrame { CGFloat verticalOffset = 0.0; CGRect hugeFrame = myCGRectHuge(theFrame); IDVBase *returnView = [[IDVBase alloc] initWithFrame:hugeFrame]; hugeFrame = myCGRectResetToZero(hugeFrame); for (IDRBase *idr in self.parts.parts) { CGRect newFrame = myCGRectDown(hugeFrame, verticalOffset); UIView *view = [idr getAsView:newFrame]; [returnView addSubview:view]; verticalOffset += view.frame.size.height; } [returnView sizeToFit]; return [returnView autorelease]; }
To avoid the unintended increment, I have to add the conditional if (view) {…} (which is a pretty good idea anyway, since it’s rather pointless, or maybe even bad, to add a nil as a subView):
- (UIView *)getAsView:(CGRect)theFrame { CGFloat verticalOffset = 0.0; CGRect hugeFrame = myCGRectHuge(theFrame); IDVBase *returnView = [[IDVBase alloc] initWithFrame:hugeFrame]; hugeFrame = myCGRectResetToZero(hugeFrame); for (IDRBase *idr in self.parts.parts) { CGRect newFrame = myCGRectDown(hugeFrame, verticalOffset); UIView *view = [idr getAsView:newFrame]; if (view) { [returnView addSubview:view]; verticalOffset += view.frame.size.height; } [returnView addSubview:view]; verticalOffset += view.frame.size.height; } [returnView sizeToFit]; return [returnView autorelease]; }
I don’t know what the moral of this story is. I assume that if I should delve into the language reference, this will turn out to be undefined behaviour. Trying to figure out what could have been defined behaviour, I can’t come up with a reasonable answer, so I guess it’s just one of those things you should avoid doing. Maybe a compiler warning would have been nice, though.