Declaring and checking/comparing (bitmask-)enums in Objective-C

28,468

Solution 1

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)?

Solution 2

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:

Solution 3

enum DownloadViewStatus {
  FileNotDownloaded = 1,
  FileDownloading = 2,
  FileDowloaded = 4
};

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

Solution 4

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

BOOL bitmaskContains(NSUInteger bitmask, NSUInteger contains) {
    return (bitmask & contains) != 0;
}
Share:
28,468

Related videos on Youtube

thibaultcha
Author by

thibaultcha

"The base of the pyramid is the largest part."

Updated on March 18, 2020

Comments

  • thibaultcha
    thibaultcha about 4 years

    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?

    • Regexident
      Regexident about 11 years
      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.
  • Mike Weller
    Mike Weller about 11 years
    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.
  • Regexident
    Regexident about 11 years
    @AhmedAlHafoudh: The article doesn't however address OP's second problem: working with bitmasks (vs. simply declaring them). See my answer.
  • Regexident
    Regexident about 11 years
    @Abizern: Thanks! Thought this question deserved a bit more explanation than had previously been provided.
  • Michael Zimmerman
    Michael Zimmerman over 10 years
    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.
  • Regexident
    Regexident over 10 years
    @MichaelZimmerman: You are absolutely right. Strange you're the first to notice. Fixed it, thanks!
  • uchuugaka
    uchuugaka over 10 years
    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
    uchuugaka over 10 years
    Yes the NS_ENUM and NS_OPTION macros are awesome.
  • Regexident
    Regexident over 10 years
    Fully aware of them. ;) (See Abizern's answer) Anyway, added a variation with NS_OPTIONS for the sake of completeness.
  • Michael Zimmerman
    Michael Zimmerman over 10 years
    Thanks for the advice uchuugaka, I use these now.
  • uchuugaka
    uchuugaka over 10 years
    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?
  • Regexident
    Regexident over 10 years
    @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.
  • uchuugaka
    uchuugaka over 10 years
    Right. I see what you mean about overflows. Perhaps it should simply be ((status & FileNotDownloaded) == FileNotDownloaded) so tag there are only two results possible.
  • Regexident
    Regexident over 10 years
    @uchuugaka: One way to do it, sure. Readability/clarity/correctness very much depends on the context, of course. ;)
  • DJm00n
    DJm00n about 4 years
    More strict (bitmask & contains) == contains - it will work even with zero contains