Declaring and checking/comparing (bitmask-)enums in Objective-C
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 OR
ing 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;
}
Related videos on Youtube
Comments
-
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 anenum
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 about 11 yearsYour
(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 about 11 yearsThe 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 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 about 11 years@Abizern: Thanks! Thought this question deserved a bit more explanation than had previously been provided.
-
Michael Zimmerman over 10 yearsYeah 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 over 10 years@MichaelZimmerman: You are absolutely right. Strange you're the first to notice. Fixed it, thanks!
-
uchuugaka over 10 yearsApple 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 over 10 yearsYes the NS_ENUM and NS_OPTION macros are awesome.
-
Regexident over 10 yearsFully aware of them. ;) (See Abizern's answer) Anyway, added a variation with
NS_OPTIONS
for the sake of completeness. -
Michael Zimmerman over 10 yearsThanks for the advice uchuugaka, I use these now.
-
uchuugaka over 10 yearsCan 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 over 10 years@uchuugaka:
BOOL
is usually defined astypedef signed char BOOL;
(most recent runtime, typedefs onto C99'sbool
, IIRC). As such any value larger thanCHAR_MAX
with its lower eight bits being all zeros, while in nature being a positive value, will turn intoNO
upon casting toBOOL
(be it ex- or implicitly). Which while technically correct, is most likely unexpected and unwanted by the developer. -
uchuugaka over 10 yearsRight. I see what you mean about overflows. Perhaps it should simply be ((status & FileNotDownloaded) == FileNotDownloaded) so tag there are only two results possible.
-
Regexident over 10 years@uchuugaka: One way to do it, sure. Readability/clarity/correctness very much depends on the context, of course. ;)
-
DJm00n about 4 yearsMore strict
(bitmask & contains) == contains
- it will work even with zerocontains