在Objective-C中声明和检查/比较(位掩码)枚举

79
你知道在Cocoa中有这样一件事,例如你可以创建一个UIView并执行以下操作:
view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

我有一个自定义的UIView,它具有多个状态,我已经在enum中定义如下:

enum DownloadViewStatus {
  FileNotDownloaded,
  FileDownloading,
  FileDownloaded
};

对于每个创建的子视图,我都会设置它的tagsubview1.tag = FileNotDownloaded;

然后,我有一个自定义的视图状态设置方法,它会执行以下操作:

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

但是我试图做的事情,就是允许这样:

subview1.tag = FileNotDownloaded | FileDownloaded;

所以我的subview1在我的视图的两个状态中都会显示。目前,由于|运算符似乎是将这两个枚举值相加,因此它并不在这两种状态下显示。

有没有办法解决这个问题?


你的 (subview.tag == viewStatus) 看起来有问题。应该是 ((subview.tag & viewStatus) != 0x0),除非你想要完全匹配。如果是这种情况,你就不需要使用位掩码了,只需要一个普通的枚举即可。详情请参见我的答案的后半部分。 - Regexident
4个回答

281

声明位掩码:

除了分配绝对值(124,...)外,您还可以这样声明位掩码(即所谓的这些):

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

或者使用现代 ObjC 的 NS_OPTIONS/NS_ENUM 宏:

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

(有关后者的更多信息,请参见 Abizern的答案
位掩码的概念通常是使用单个比特设置来定义每个枚举值。
因此,将两个值进行 OR 运算会执行以下操作:
DownloadViewStatus status = FileNotDownloaded | FileDownloaded; // => 00000101

这相当于:

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

比较位掩码:

在检查位掩码时需要注意以下一点:

检查完全相等性:

假设 status 被初始化为以下值:

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

如果您想检查状态等于文件未下载,可以使用:

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

这等同于:

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

检查"会员身份":

如果您想要检查 status 是否仅包含 FileNotDownloaded,您需要使用以下代码:

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

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

看到微小的差异了吗(以及为什么你当前的“if”表达式可能是错误的)?


是的,但您正在将二进制值格式化为十六进制值(前缀为0x)。位掩码在位级别上工作。这是一个简单的错误,我相信您甚至没有注意到它。但是有人可能会看到它并错误地认为您可以在枚举中拥有最多8个选项,而实际上您最多可以拥有32个不同的选项。更正:FileNotDownloaded = (0x1 << 0), // => %...00000001 等。 - Michael Zimmerman
1
苹果提供了一对很棒的宏NS_ENUM和NS_OPTION,用于枚举和位掩码声明。建议使用它们。可以查看NSHipster网站获取更多详细描述。 - uchuugaka
2
完全了解它们。;) (请参见Abizern的答案)无论如何,为了完整起见,添加了一个带有NS_OPTIONS的变体。 - Regexident
你可以详细讲解一下为什么需要 != 0 吗?如果没有非零条件,我似乎无法让它给出错误的结果。我是否忽略了位掩码方面的一些明显/晦涩的东西? - uchuugaka
1
好的。我明白你说的溢出问题了。也许应该简单地写成((status&FileNotDownloaded)== FileNotDownloaded),这样标记就只有两种可能的结果。 - uchuugaka
显示剩余5条评论

20

虽然@Regexident提供了一个出色的答案,但我必须提及使用NS_OPTIONS声明枚举选项的现代Objective-C方式:

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

更多参考资料:


是的,NS_ENUM和NS_OPTION宏非常棒。 - uchuugaka

1

您可以使用有用的函数进行位掩码检查,以提高可读性。

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

更严格的 (bitmask & contains) == contains - 即使 contains 为零也能正常工作 - DJm00n

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

这将使你有效地执行按位或和按位与运算。

4
定义值的标准方式是1 << 01 << 11 << 2等。这使得你在处理位和掩码。 - Mike Weller
1
@AhmedAlHafoudh:然而,该文章未解决 OP 的第二个问题:如何处理位掩码(而不仅仅是声明)。请看我的回答。 - Regexident

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接