Next article: Friday Q&A 2011-09-02: Let's Build NSAutoreleasePool
Previous article: See Me Speak at Voices That Matter in Boston
Tags: c fridayqna objectivec
The inevitable rotation of the Earth means that it's once again time for another Friday Q&A. For today's edition, Jose Vazquez suggested that I discuss namespaced constants and functions in C.
Traditional Constants
There are three standard techniques for creating constants in C: #define
, const
global variables, and enum
.
The use of #define
is straightforward. Simply create a macro with the name and value you want to use:
#define MAAnswerToTheQuestion 42
#define MAThingDidChangeNotification @"MAThingDidChangeNotification"
The main downside to this approach is that it doesn't interact nicely with the debugger. Since #define
is a preprocessor construct, the debugger doesn't see MAAnswerToTheQuestion
, just 42
, so trying to get it to print the value of MAAnswerToTheQuestion
won't work. It's also a bit ugly, and you can get into some serious trouble if you forget to parenthesize a more complicated expression like #define MAAnswerToTheQuestion 40 + 2
.
Global variable constants are a little more complex, but should be familiar since they're the same as a regular global variable, just with the const
keyword added. This follows a more typical C pattern of a declaration in the header, then a definition in the implementation file. The header looks like this:
extern const int MAAnswerToTheQuestion;
extern NSString * const MAThingDidChangeNotification;
Note the odd position of the const
keyword in the second one. Writing the more natural type of const NSString *
won't have the intended effect. That declares a pointer to a const NSString
, when what we want is a const
pointer to a regular NSString
.
The definition in the implementation file is much the same, minus the 'extern' and with an initializer:
const int MAAnswerToTheQuestion = 42;
NSString * const MAThingDidChangeNotification = @"MAThingDidChangeNotification";
This technique is a little more wordy but works more like you might expect it to. The symbols will be visible to the debugger and won't have strange interactions with the preprocessor.
The enum
technique only works for integers, but can be nice for them:
enum MAConstants
{
MAAnswerToTheQuestion = 42,
MAMusketeers = 3,
MACircumferenceOfEarth = 199211 // in furlongs
};
This ends up being reasonably nice, but is somewhat unnatural for constants which aren't part of a single group, as enums
are typically used.
Namespaced Constants
Like other global symbols in Objective-C, it's normal to prefix constants with the name of the class that they're associated with or other pertinent info. This not only helps organize the constants and tell the reader where they're from, but also prevents inadvertent naming conflicts between two constants with a similar purpose from different sections of the program.
As an example, let's imagine a class called MANotifyingArray
which posts notifications when its contents are altered. The names of these notifications are declared as string constants, along with some userInfo
keys for more information:
const NSString * MANotifyingArrayDidAddObjectNotification;
const NSString * MANotifyingArrayDidChangeObjectNotification;
const NSString * MANotifyingArrayDidRemoveObjectNotification;
// userInfo key pointing to NSNumber
const NSString * MANotifyingArrayIndexChangedKey;
// userInfo key containing the changed object
const NSString * MANotifyingArrayObjectChangedKey;
And then the implementation file would have pretty much the same thing again, slightly different. This is really verbose and ugly, and there's a ton of repetition.
I do a fair bit of Python programming as well, and in Python I can stuff constants into a class to reduce redundancy while preserving organization and preventing conflicts. The above might look like this in Python:
class MANotifyingArray:
class notifications:
didAddObject = 'didAddObject'
didChangeObject = 'didChangeObject'
didRemoveObject = 'didRemoveObject'
class keys:
indexChanged = 'indexChanged'
objectChanged = 'objectChanged'
These values could then be accessed by writing code like MANotifyingArray.notifications.didAddObject
or MANotifyingArray.notifications.keys.indexChanged
. This is much easier to read and write and also gives us a nice hierarchy.
Wouldn't it be nice if we could do this in Objective-C as well? It turns out that we can, or very nearly.
It's not possible to use a class for this purpose as the Python example does, but we can use a struct. The notifications above would look like this:
// in the header
extern const struct MANotifyingArrayNotificationsStruct
{
NSString *didAddObject;
NSString *didChangeObject;
NSString *didRemoveObject;
} MANotifyingArrayNotifications;
// in the implementation
const struct MANotifyingArrayNotificationsStruct MANotifyingArrayNotifications = {
.didAddObject = @"didAddObject",
.didChangeObject = @"didChangeObject",
.didRemoveObject = @"didRemoveObject"
};
The .didAddObject = ...
syntax is a new feature of C99 which allows explicitly initializing individual fields of a struct. It's not strictly necessary here, but it makes this code considerably more clear.
These constants can then be accessed by writing code like MANotifyingArrayNotifications.didAddObject
. How nice!
We can go further and even replicate the nested hierarchy of the Python version. Here's what the code looks like with that added in:
// in the header
extern const struct MANotifyingArrayNotifications
{
NSString *didAddObject;
NSString *didChangeObject;
NSString *didRemoveObject;
struct
{
NSString *indexChanged;
NSString *objectChanged;
} keys;
} MANotifyingArrayNotifications;
// in the implementation
const struct MANotifyingArrayNotifications MANotifyingArrayNotifications = {
.didAddObject = @"didAddObject",
.didChangeObject = @"didChangeObject",
.didRemoveObject = @"didRemoveObject",
.keys = {
.indexChanged = @"indexChanged",
.objectChanged = @"objectChanged"
}
};
The nested constants can then be accessed just as you would expect, with NSNotifyingArrayNotifications.keys.indexChanged
and similar.
Overall, this ends up working nicely. A great deal of the redundancy of the standard global variable approach is eliminated, and the constants are placed in a hierarchy that's easy to understand.
It's certainly not perfect. There's substantial redundancy in having to declare a separate struct
type in addition to the variable. However, the pros substantially outweigh the cons in my view.
Namespaced Functions
But wait, there's more! The same basic technique can be used to create namespaced functions as well. Plain functions aren't found as often in typical Cocoa/Objective-C code, but they still show up. Cocoa itself contains many helpful functions like NSSelectorFromString
, NSPointInRect
, and the ubiquitous NSLog
.
We can use the same basic struct technique to namespace these functions. The struct will contain function pointers, and then when initializing the struct, we'll just have to implement the function privately and then point the function pointer at the real function.
For an example, let's make a few extra utility functions for NSRect
:
extern const struct MARectUtils
{
NSPoint (*topLeft)(NSRect r);
NSPoint (*topRight)(NSRect r);
NSPoint (*bottomLeft)(NSRect r);
NSPoint (*bottomRight)(NSRect r);
} MARectUtils;
For the implementation, we'll write these as normal, static
functions, and then finally initialize the struct with them:
static NSPoint topLeft(NSRect r)
{
return r.origin;
}
static NSPoint topRight(NSRect r)
{
return NSMakePoint(NSMaxX(r), NSMinY(r));
}
static NSPoint bottomLeft(NSRect r)
{
return NSMakePoint(NSMinX(r), NSMaxY(r));
}
static NSPoint bottomRight(NSRect r)
{
return NSMakePoint(NSMaxX(r), NSMaxY(r));
}
const struct MARectUtils MARectUtils = {
.topLeft = topLeft,
.topRight = topRight,
.bottomLeft = bottomLeft,
.bottomRight = bottomRight
};
Code can call these functions like this:
NSPoint corner = MARectUtils.bottomRight([self frame]);
Just like with constants, this provides nice grouping and categorization for these functions.
Conclusion
Namespacing constants and functions in C is an unusual technique, and likely to frighten people who aren't comfortable seeing new things. However, I believe it could make code cleaner and easier to understand, and in any case is worth some consideration.
That wraps things up for today. Come back in two weeks for the next one. As always, Friday Q&A is driven by reader ideas, so if you have a topic that you would like to see covered here, send it in!
Comments:
#define
for __attribute__ ((unused))
that I use on static consts.Why Apple went to all the trouble of adding KVC dot-notation when they could have added namespaces I just don't know. I'm sure they had their reasons, but on Tiger I don't recall ever saying to myself "Gah I hate having to type 'objjctForKey:' over and over!" but I DO still say to myself "Gah what should I prefix my class names with on THIS project?"
When I do it in practice, and have been for years, I get called “insane,” “iditotic,” and other insulting intonations starting with “i.”
But, onto the subject; here’s quite a few examples of this applied in real-life:
My equivalent of a “header”: https://github.com/elliottcable/Paws.c/blob/323628/Source/Types/fork/LL.c#L33-63
The runtime initialization of the struct with the relevant functions: https://github.com/elliottcable/Paws.c/blob/323628/Source/Types/fork/LL.c#L83-127
Finally, the implementation of the functions themselves: https://github.com/elliottcable/Paws.c/blob/323628/Source/Types/fork/LL.c#L128-229
As usual, this is another excellent post! Very intriguing. Now I want to refactor all my #defines and consts with namespaced constants! :) (Honestly, NSBlog is one of my main sources where I find inspirations/motivations for refactoring my code. I learn the techniques here and apply them to my code. It's the Friday I most look forward to :)
In regards to Namespaced Functions, I have a question for Mike and all you experts:
Although "namespaced functions" seem cool, I wonder if it's all that practical in the world of Cocoa. If I'm writing a project mostly in C (not taking advantage of the Cocoa framework), I can definitely see the benefit of namespaced functions. But if I'm writing an object-oriented solution, wouldn't it be better to simply write a wrapper class? Even though a class in ObjC doesn't give you the namespace notation, it offers so much more, especially when considering the principle of Separation of Concern? I guess my real question is: is there a scenario where I can use this technique (namespaced functions with struct) even in a project that's entirely composed in classes? Please enlighten me :) Thanks!
Regarding namespaced functions, I completely agree that writing classes is generally going to be a better approach. Namespaced functions aren't a replacement for classes, but rather a small improvement on regular C functions when used in this context. As you've no doubt noticed, C functions aren't very commonly used in Cocoa code. Where they *are* used, this technique could come in handy, but it's not going to happen very often.
If you wanted to go completely overboard, you could actually do a sort of namespaced class by putting a
Class
variable in a struct and then assigning it to a real class. But, probably not a good idea there. namespace MANotifyingArrayNotifications
{
NSString *didAddObject;
NSString *didChangeObject;
}
MANotifyingArrayNotifications::didAddObject
is equivalent (more or less) to:
struct MANotifyingArrayNotifications
{
NSString *didAddObject;
NSString *didChangeObject;
};
MANotifyingArrayNotifications.didAddObject
Of course, namespaces are more flexible, but are not less (or more) prone to collision (ie, in either you can name an object at level X the same as another object at level X), although the flexibility of namespaces does make it easier to contain disparate items vs using a struct (ie, no need for a function to assign the values for instance).
When you add a new value in the header and you forget to add it in the implementation file, the compiler will no detect the error until runtime. If you use standard globals, the linker will tell you there is a missing symbol.
You may expect a "missing field initialization" warning in the struct definition, but unfortunately, using the c99 syntax inhibits such warning.
const struct MANotifyingArrayNotifications MANotifyingArrayNotifications = {
.didAddObject = @"MANotifyingArrayDidAddObject",
.didChangeObject = @"MANotifyingArrayDidChangeObject",
.didRemoveObject = @"MANotifyingArrayDidRemoveObject",
.keys = {
.indexChanged = @"MANotifyingArrayIndexChanged",
.objectChanged = @"MANotifyingArrayObjectChanged"
}
};
And after that I think struct doesn't give any pros.
// your usual enum
typedef enum StoogeType {
StoogeTypeMoe = 0,
StoogeTypeLarry = 1,
StoogeTypeCurly = 2
} StoogeType;
// a mirroring struct
struct StoogeStruct {
StoogeType moe;
StoogeType larry;
StoogeType curly;
};
// how it gets accessed
extern const struct StoogeStruct StoogeTypes;
Then you could write something like
StoogeType stooge = StoogeTypes.moe;
Right now, it seems like a lot of work for little benefit, so I doubt the practical use, but ... good to know.
Link: https://github.com/claybridges/EnumDotSyntax
Comments RSS feed for this page
Add your thoughts, post a comment:
Spam and off-topic posts will be deleted without notice. Culprits may be publicly humiliated at my sole discretion.
Regarding your crazy structs idea, I think it'd be a good idea to fold this kind of thing into the language. Maybe such a language could feature both an explicit namespace feature and an enhanced form of structs with inheritance, visibility control, etc. Someone should get on that!