Sunday, December 13, 2009

Using NSSet instead of plain old C-Enums and bitwise operations

In my first blog post I would like to convince you that using C-Enums and bit operations when writing Objective-C code is not such a good idea anymore. In order to convince you I will first show you how we deal with enumerations today. In the second act I will explain why the current handling of enumerations is bad. And at last I will show you a alternative that you can use to improve you enum and bit operation code.

Now, please have a look at NSView's autoresizingMask property. The autoresizingMask lets a developer specify how the view will change it's frame when it is being resized. When setting the auto resizing behavior you have create a mask using bitwise OR operations. Apple explains that in the documentation quite well:

- (void)setAutoresizingMask:(NSUInteger)mask

Parameters
mask
An integer bit mask. mask can be specified by combining using the C bitwise OR operator any of the options described in "Resizing masks".

The possible "values" that can be used to composite an auto resizing mask are also well documented:

enum {
   NSViewNotSizable     = 0,
   NSViewMinXMargin     = 1,
   NSViewWidthSizable   = 2,
   NSViewMaxXMargin     = 4,
   NSViewMinYMargin     = 8,
   NSViewHeightSizable  = 16,
   NSViewMaxYMargin     = 32
};

So, how does setting the auto resizing mask typically look like? Let's recap:

[aView setAutoresizingMask:(NSViewWidthSizable |
                            NSViewHeightSizable)];

One questions immediately pops up: Why does it have to be so complicated? There are good reasons.

  • Bit masks are usually very "small" and thus save memory.
  • Even though a resizing mask is nothing more than a single number it can describe nearly an infinite number of possible combinations. There is no need for setters/getters like -setHeightSizable:(BOOL)flag, ...

What is wrong with that? At the time Objective-C and Cocoa were first invented CPU speed and available memory were very limited resources. Thus using enumerations and bitwise operations to describe complex properties was a brilliant idea. But today, these concepts seem a little bit outdated, at least on the desktop platform. So let's improve that.

Let's try to describe to autoresizingMask-property in a modern way. The first step is to find an equivalent to a "mask". The most obvious equivalent is a simple mathematical set that is able to contain the possible mask values. Fortunately Objective-C (Foundation) does already know what a set is. A set is just an instance of NSSet. Brilliant. But wait! It is not possible to add plain old C objects into a NSSet. Well, we don't have to. Instead of assigning the mask values like NSViewHeightSizable unsigned integer values we assign each of them a constant NSString.

NSString *kViewNotSizable = @"kViewNotSizable";
NSString *kViewMinXMargin = @"kViewMinXMargin";
NSString *kViewWidthSizable = @"kViewWidthSizable";
NSString *kViewMaxXMargin = @"kViewMaxXMargin";
NSString *kViewMinYMargin = @"kViewMinYMargin";
NSString *kViewHeightSizable = @"kViewHeightSizable";
NSString *kViewMaxYMargin = @"kViewMaxYMargin";

We would also have to rewrite the accessors for the auto resizing mask to use NSSet.

- (void)setAutoresizingMask:(NSSet *)mask;
- (NSSet *)autoresizingMask;

Thats basically it. Setting the auto resizing mask by using this new pattern would look like this:

[aView setAutoresizingMask:[NSSet setWithObjects:
                                    kViewWidthSizable,
                                    kViewHeightSizable, nil];

No more bitwise operations. But there is more: By using NSSet we can now use the "operations" that are already built in NSSet. Let me give you just two examples:

Checking if the view should resize it's height:

if([[aView autoresizingMask] containsObject:kViewHeightSizable])

Checking if the auto resizing mask is "valid":

 NSSet *maxMask = [NSSet setWithObjects:
                   kViewNotSizable,
                   kViewMinXMargin,
                   kViewWidthSizable,
                   kViewMaxXMargin,
                   kViewMinYMargin,
                   kViewHeightSizable,
                   kViewMaxYMargin];

 if([[aView autoresizingMask] isSubsetOfSet:maxMask])

I hope you get the idea. Sure, you have to write a few extra lines of code. But it becomes much more readable. In addition to that key value coding and key value observing are possible and safe without doing additional work. If you want that the size of a view should stay the same just assign is an empty set. You don't have to know or lookup the value that represents that.

I suggest that you try this pattern for yourself where you can. I am pretty sure that you will fall in love with it. You don't have to worry about performance of memory related problems because I am sure that it doesn't have a significant impact. When developing for the iPhone/iPod platform you may want to consider not using this pattern. I can imagine that it could have a negative effect there. But I haven' tried it. That is up to you. :)

Let me know that you think about this "pattern". Will you try it? Are you already using it? Post a comment or send me an email.

6 comments:

  1. Hmmm, I'm not sure, I actually like the bitmask approach. It's very simple to assign (Bitwise OR) and to check (Bitwise AND). The NSSet approach does not appear to be more readable than the bitmasks, actually I think that the bitmask is more readable; at least in the example of the autoresizingMask.

    ReplyDelete
  2. @pp: Do you really think someone who has never seen bitwise operations (and who does not understand the magic behind it) agrees? In my opinion these bitwise operations are just a hack. Of course with NSSet you have to write more. I am sure this could be improved in some way. But replacing bitsets with NSSet is like replacing ints, floats, ... with NSNumber (which is in many cases a good thing).

    ReplyDelete
    Replies
    1. @Christian: You could say the same thing about any feature of any lanugage: that it is complicated if you haven't seen it before. In my view bitwise operator is a completly basic feature of many programming languages, which most people will learn in their first year at Uni. To call bitwise operators magical seems very strange to me. A lot of people have more problems with Objective-C message passing syntax I believe than bitwise operators which syntax wise look the same in most algol inspired languages.

      Delete
  3. You're certainly right about people unfamiliar with bitwise operations. However I wouldn't consider them a hack, they work pretty well, even in the Unix file system. Besides Obj-C I'm personally even using them in some PHP classes I made, however providing accessor methods (e.g. "user->canAccessWhatever()").

    It's certainly a question about taste, a definitve answer is probably not possible. :)

    ReplyDelete
  4. Sure. It is in most cases a question about taste.

    ReplyDelete
  5. Apple's new "NS_ENUM" and "NS_Options" macros may be of help when working with enum and bit-masks. See this explanation: http://www.NSHipster.com/ns_enum-ns_options/

    ReplyDelete