79

You know in Cocoa there is this thing, for example you can create a UIView and do:

view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

I have a custom UIView with multiple states, which I have defined in an enum like this:

enum DownloadViewStatus {
  FileNotDownloaded,
  FileDownloading,
  FileDownloaded
};

For each created subview, I set its tag: subview1.tag = FileNotDownloaded;

Then, I have a custom setter for the view state which does the following:

for (UIView *subview in self.subviews) {
  if (subview.tag == viewStatus)
    subview.hidden = NO;
  else
    subview.hidden = YES;
}

But what I am trying to do, is to allow this:

subview1.tag = FileNotDownloaded | FileDownloaded;

So my subview1 shows up in two states of my view. Currently, it doesn't show up in any of those two states since the | operator seems to add the two enum values.

Is there a way to do that?

Corwin Newall
  • 508
  • 8
  • 18
thibaultcha
  • 1,320
  • 2
  • 15
  • 27
  • Your `(subview.tag == viewStatus)` looks wrong to me. Should be `((subview.tag & viewStatus) != 0x0)`, unless you want to just check for exact matching. In which case you wouldn't need a bitmask in the first place, but just a plain old enum. See second half of my answer. – Regexident Apr 23 '13 at 11:17

4 Answers4

281

Declaring Bitmasks:

Alternatively to assigning absolute values (1, 2, 4, …) you can declare bitmasks (how these are called) like this:

typedef enum : NSUInteger {
  FileNotDownloaded = (1 << 0), // => 00000001
  FileDownloading   = (1 << 1), // => 00000010
  FileDownloaded     = (1 << 2)  // => 00000100
} DownloadViewStatus;

or using modern ObjC's NS_OPTIONS/NS_ENUM macros:

typedef NS_OPTIONS(NSUInteger, DownloadViewStatus) {
  FileNotDownloaded = (1 << 0), // => 00000001
  FileDownloading   = (1 << 1), // => 00000010
  FileDownloaded    = (1 << 2)  // => 00000100
};

(see Abizern's answer for more info on the latter)

The concept of bitmasks is to (usually) define each enum value with a single bit set.

Hence ORing two values does the following:

DownloadViewStatus status = FileNotDownloaded | FileDownloaded; // => 00000101

which is equivalent to:

  00000001 // FileNotDownloaded
| 00000100 // FileDownloaded
----------
= 00000101 // (FileNotDownloaded | FileDownloaded)

Comparing Bitmasks:

One thing to keep in mind when checking against bitmasks:

Checking for exact equality:

Let's assume that status is initialized like this:

DownloadViewStatus status = FileNotDownloaded | FileDownloaded; // => 00000101

If you want to check if status equals FileNotDownloaded, you can use:

BOOL equals = (status == FileNotDownloaded); // => false

which is equivalent to:

   00000101 // (FileNotDownloaded | FileDownloaded)
== 00000100 // FileDownloaded
-----------
=  00000000 // false

Checking for "membership":

If you want to check if status merely contains FileNotDownloaded, you need to use:

BOOL contains = (status & FileNotDownloaded) != 0; // => true

   00000101 // (FileNotDownloaded | FileDownloaded)
&  00000100 // FileDownloaded
-----------
=  00000100 // FileDownloaded
!= 00000000 // 0
-----------
=  00000001 // 1 => true

See the subtle difference (and why your current "if"-expression is probably wrong)?

Community
  • 1
  • 1
Regexident
  • 29,441
  • 10
  • 93
  • 100
  • @Abizern: Thanks! Thought this question deserved a bit more explanation than had previously been provided. – Regexident Apr 23 '13 at 11:19
  • Yeah but you're formatting the binary values as hexadecimal values (preceded by 0x). Bitmasks work on the bit level. Simple mistake, I'm sure you didn't even notice it. But someone might look at that and incorrectly assume that you can have a max of 8 options per enum, when you can actually have a maximum of 32 distinct options. Correction: `FileNotDownloaded = (0x1 << 0), // => %...00000001` etc. – Michael Zimmerman Nov 28 '13 at 20:49
  • @MichaelZimmerman: You are absolutely right. Strange you're the first to notice. Fixed it, thanks! – Regexident Nov 29 '13 at 14:33
  • 1
    Apple provides a wonderful pair of macros NS_ENUM and NS_OPTION for enum and bit mask declarations. Use them. See NSHipster site for some good descriptions. – uchuugaka Nov 29 '13 at 15:54
  • 2
    Fully aware of them. ;) (See Abizern's answer) Anyway, added a variation with `NS_OPTIONS` for the sake of completeness. – Regexident Dec 01 '13 at 20:25
  • Thanks for the advice uchuugaka, I use these now. – Michael Zimmerman Dec 05 '13 at 22:48
  • Can you expand a bit on why the need for the `!= 0` ? I cannot seem to get it to give a false positive without the not zero condition. Am I missing something obvious/obtuse about bit masks? – uchuugaka Jan 20 '14 at 14:48
  • @uchuugaka: `BOOL` is usually defined as `typedef signed char BOOL;` (most recent runtime, typedefs onto C99's `bool`, IIRC). As such any value larger than `CHAR_MAX` with its lower eight bits being all zeros, while in nature being a positive value, will turn into `NO` upon casting to `BOOL` (be it ex- or implicitly). Which while technically correct, is most likely unexpected and unwanted by the developer. – Regexident Jan 20 '14 at 21:15
  • 1
    Right. I see what you mean about overflows. Perhaps it should simply be ((status & FileNotDownloaded) == FileNotDownloaded) so tag there are only two results possible. – uchuugaka Jan 20 '14 at 23:40
  • @uchuugaka: One way to do it, sure. Readability/clarity/correctness very much depends on the context, of course. ;) – Regexident Jan 21 '14 at 11:44
20

While @Regexident has provided an excellent answer - I must mention the modern Objective-C way of declaring Enumerated options with NS_OPTIONS:

typedef NS_OPTIONS(NSUInteger, DownloadViewStatus) {
  FileNotDownloaded = 0,
  FileDownloading   = 1 << 0,
  FileDownloaded    = 1 << 1
};

Further Reference:

pkamb
  • 33,281
  • 23
  • 160
  • 191
Abizern
  • 146,289
  • 39
  • 203
  • 257
1
enum DownloadViewStatus {
  FileNotDownloaded = 1,
  FileDownloading = 2,
  FileDowloaded = 4
};

This will let you perform bitwise OR's and AND's effectively.

mah
  • 39,056
  • 9
  • 76
  • 93
  • 4
    The standard way to define the values is `1 << 0`, `1 << 1`, `1 << 2` etc. This makes it clear you are working with bits and masks. – Mike Weller Apr 23 '13 at 10:58
  • 1
    @AhmedAlHafoudh: The article doesn't however address OP's second problem: working with bitmasks (vs. simply declaring them). See my answer. – Regexident Apr 23 '13 at 11:15
1

Useful function you can use for bitmask checking to improve readability.

BOOL bitmaskContains(NSUInteger bitmask, NSUInteger contains) {
    return (bitmask & contains) != 0;
}
Renetik
  • 5,887
  • 1
  • 47
  • 66