You probably know CGRectMake
, but did you know it’s not the only way to make rectangles? It’s not even the best way, really.
There’s also C99 initializer syntax.
The main advantage to the C99 syntax is that it gives you some very Objective-C like syntax, where the fields are close to the values rather than implied by positioning. (That’s not to say this is intentionally similar, or that it’s the only advantage. But it is nice.) It also provides type checking, and since fields are named it catches drifts in meaning that you otherwise wouldn’t catch.
It’s sometimes slightly more typing, but I use it everywhere now.
The syntax
Consider the CGRectMake
way to make a rectangle:
CGRect a = CGRectMake(a+c/2, b+d/2, c, d);
In order to understand this, you need to understand the order of the parameters. You also need to be able to catch the commas easily with your eyes. In this case, that’s pretty easy, but if the expressions were more complicated you’d probably be storing them in a temporary variable first.
The C99 way:
CGRect a = (CGRect){
.origin.x = a+c/2,
.origin.y = b+d/2,
.size.width = c,
.size.height = d
};
It’s longer, but it’s more explicit. It’s also very easy to follow what is assigned to what, no matter how long the expression are. It’s also more like an Objective-C method. After all, if CGRect
was a class, it would probably look like this:
CGRect a = [[CGRect alloc] initWithOriginX:x originY:y width:w height:h];
Note that any field you don’t explicitly initialize is initialized to zero. This code, for instance, will create a rectangle with an origin
of 0,0.
CGRect smallRect = (CGRect){
.size = mySize
}
The C99 syntax is a nice compromise; it’s more compact than class construction, but still names field values.
Catching changes
Naming fields might seem like extra work, but it’s also extra safety. If the names or meanings of the parameters change, you’re going to get compiler errors. The compiler will help you find these, now! And if you write new code based on the old rules, compiler errors will point out what you’ve done.
This isn’t much of a concern for CGRect
; it is how it is. Apple is probably never going to redefine it. But many years ago, QuickDraw used edges for its Rect
structure instead of points. If you were still trying to break that habit, the compiler would complain every time you did this:
CGRect a = (CGRect){
.left = 0,
.right = 100,
.top = 0,
.bottom = 100
};
In your own code, this may be an active concern to you. You may have structs
(or use C libraries that have structs
) that will change layout in the future.
Type checking
That’s only the start of C99 initializer coolness, though.
You can also do things like this:
CGRect a = (CGRect){
.origin = myOrigin,
.size = computedSize
};
Here, you’re building a rectangle using a CGPoint
and CGSize
. The compiler understands that .origin
expects a CGPoint
, and .size
expects a CGSize
. You’ve provided that. All’s gravy.
The equivalent code would be CGRectMake(myOrigin.x, myOrigin.y, size.width, size.height)
. By using CGRectMake
you’re no longer expressing the same kind of meaning to the compiler. It can’t stop you from assigning part of the size to the origin. It also won’t stop you from assigning the width to the height. It doesn’t even give you a good clue about which is the X and Y; if you’ve used APIs that provide vertical coordinates first, you’ll get it wrong. If you’re used to APIs that provide two points or something else (like QuickDraw’s edges) instead of a point and size you’ll also get a compiler error.
You can assign part from a structure and part from floats as well:
CGRect a = (CGRect){
.origin = myOrigin,
.size.width = c,
.size.height = d
};
Drawbacks
As I said, this is more typing than CGRectMake
. What I didn’t say is that it’s even more typing than you expect, because as of Xcode 4.6, you still can’t autocomplete the field names. You really do need to type all that extra code.
I think this is a good pay off for being able to read the code so easily later, but you may disagree.
Conclusion
The CGRectMake
function predates C99. I have no evidence to this effect, but I think if C99 had come first CGRectMake
probably wouldn’t exist at all; it’s the sort of crusty function you write when your language has no direct way to perform the initialization. But now it does.
This doesn’t only apply to CGRect
. Any of the simple struct
types can be initialized this way, such as CGPoint
, CGSize
and NSRange
.
Avantages:
- Explicit: You’ve named the parameters.
- Type safe: You’ve provided enough meaning to the compiler that it can help you not screw up.
- Easy to read: Especially with longer expressions, you’ve eliminated needing to scan for commas when you read the code later.
Disadvantages:
- More typing: You read code a lot more than you type it, so I think this is a great tradeoff.
Averaged over time, anything you can screw up you eventually will. Using defensive techniques makes sense. Try it for a while; I think you’ll like it!
Special thanks to:
-
Peter Hosey for reminding me how QuickDraw worked and inspiring “Catching changes.”
-
Luke Bernardi for reminding me that Xcode’s autocompletion can’t suggest field names.
-
BJ Homer for tipping me that unnamed fields are initialized to 0.