Объявление и проверка/сравнение (bitmask-)enums в Objective-C

Вы знаете, что в Cocoa есть такая вещь, например, вы можете создать UIView и сделать:

view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

У меня есть пользовательский UIView с несколькими состояниями, которые я определил в enum следующим образом:

enum DownloadViewStatus {
  FileNotDownloaded,
  FileDownloading,
  FileDownloaded
};

Для каждого созданного подвида я задаю его tag: subview1.tag = FileNotDownloaded;

Затем у меня есть собственный сеттер для состояния просмотра, который делает следующее:

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

Но то, что я пытаюсь сделать, это разрешить следующее:

subview1.tag = FileNotDownloaded | FileDownloaded;

Итак, мой subview1 отображается в двух состояниях моего зрения. В настоящее время он не отображается ни в одном из этих двух состояний, поскольку оператор |, похоже, добавляет два значения перечисления.

Есть ли способ сделать это?


person thibaultcha    schedule 23.04.2013    source источник
comment
Ваш (subview.tag == viewStatus) кажется мне неправильным. Должно быть ((subview.tag & viewStatus) != 0x0), если только вы не хотите просто проверить точное совпадение. В этом случае вам не понадобится битовая маска, а просто старое простое перечисление. Смотрите вторую половину моего ответа.   -  person Regexident    schedule 23.04.2013


Ответы (4)


Объявление битмаски:

В качестве альтернативы назначению абсолютных значений (1, 2, 4, …) вы можете объявить битовые маски. (как они называются) вот так:

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 answer для получения дополнительной информации о последнем)

Концепция битовых масок заключается в том, чтобы (обычно) определять каждое значение перечисления с помощью одного набора битов.

Следовательно, ORing два значения делает следующее:

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

что эквивалентно:

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

Сравнение битмасок:

При проверке битовых масок следует помнить одну вещь:

Проверка на точное равенство:

Предположим, что статус инициализирован следующим образом:

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

Если вы хотите проверить, является ли status равным FileNotDownloaded, вы можете использовать:

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

Видите тонкую разницу (и почему ваше текущее выражение «если», вероятно, неверно)?

person Regexident    schedule 23.04.2013
comment
@Abizern: Спасибо! Думал, что этот вопрос заслуживает немного большего объяснения, чем было дано ранее. - person Regexident; 23.04.2013
comment
Да, но вы форматируете двоичные значения как шестнадцатеричные значения (с предшествующим 0x). Битмаски работают на битовом уровне. Простая ошибка, я уверен, что вы даже не заметили этого. Но кто-то может посмотреть на это и неправильно предположить, что у вас может быть максимум 8 опций для каждого перечисления, тогда как на самом деле у вас может быть максимум 32 различных опции. Исправление: FileNotDownloaded = (0x1 << 0), // => %...00000001 и т. д. - person Michael Zimmerman; 29.11.2013
comment
@MichaelZimmerman: Вы абсолютно правы. Странно, что ты первый это заметил. Исправил, спасибо! - person Regexident; 29.11.2013
comment
Apple предоставляет замечательную пару макросов NS_ENUM и NS_OPTION для объявлений перечисления и битовой маски. Используй их. См. сайт NSHipster для некоторых хороших описаний. - person uchuugaka; 29.11.2013
comment
Полностью о них осведомлен. ;) (См. Ответ Абизерна) В любом случае, для полноты картины добавлен вариант с NS_OPTIONS. - person Regexident; 02.12.2013
comment
Спасибо за совет uchuugaka, я использую их сейчас. - person Michael Zimmerman; 06.12.2013
comment
Не могли бы вы немного рассказать о том, зачем нужен != 0 ? Кажется, я не могу заставить его дать ложное срабатывание без ненулевого условия. Я упустил что-то очевидное/тупое в битовых масках? - person uchuugaka; 20.01.2014
comment
@uchuugaka: BOOL обычно определяется как typedef signed char BOOL; (самая последняя среда выполнения, определения типов на bool C99, IIRC). Таким образом, любое значение, превышающее CHAR_MAX, в котором восемь младших битов равны нулю, хотя по своей природе является положительным значением, превратится в NO при приведении к BOOL (будь то явно или неявно). Что технически правильно, но, скорее всего, неожиданно и нежелательно для разработчика. - person Regexident; 21.01.2014
comment
Верно. Я понимаю, что вы имеете в виду о переполнении. Возможно, это должно быть просто ((status & FileNotDownloaded) == FileNotDownloaded), поэтому пометьте возможны только два результата. - person uchuugaka; 21.01.2014
comment
@uchuugaka: Один из способов сделать это, конечно. Читаемость/ясность/правильность, конечно, во многом зависит от контекста. ;) - person Regexident; 21.01.2014

Хотя @Regexident предоставил отличный ответ, я должен упомянуть современный способ Objective-C объявления параметров перечисления с помощью NS_OPTIONS:

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

Дополнительная ссылка:

person Abizern    schedule 23.04.2013
comment
Да, макросы NS_ENUM и NS_OPTION потрясающие. - person uchuugaka; 29.11.2013

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

Это позволит вам эффективно выполнять побитовые операции ИЛИ и И.

person mah    schedule 23.04.2013
comment
Стандартный способ определения значений — 1 << 0, 1 << 1, 1 << 2 и т. д. Это дает понять, что вы работаете с битами и масками. - person Mike Weller; 23.04.2013
comment
@AhmedAlHafoudh: статья, однако, не затрагивает вторую проблему OP: работу с битовыми масками (по сравнению с простым их объявлением). Смотрите мой ответ. - person Regexident; 23.04.2013

Полезная функция, которую вы можете использовать для проверки битовой маски для улучшения читабельности.

BOOL bitmaskContains(NSUInteger bitmask, NSUInteger contains) {
    return (bitmask & contains) != 0;
}
person Renetik    schedule 18.03.2019
comment
Более строгий (bitmask & contains) == contains - будет работать даже с нулевым contains - person DJm00n; 17.04.2020