Оголошення та перевірка / порівняння (bitmask-) перелічень у Objective-C


79

Ви знаєте, що в какао є ця річ, наприклад, ви можете створити 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з’являється у двох станах, на мій погляд. В даний час він не відображається в жодному з цих двох станів, оскільки |оператор, здається, додає два значення переліку.

Чи є спосіб зробити це?


Ваша (subview.tag == viewStatus)зовнішність неправильно для мене. Має бути ((subview.tag & viewStatus) != 0x0), якщо ви не хочете просто перевірити точність відповідності. У цьому випадку вам спочатку не знадобиться бітова маска, а просто звичайний старий перелік. Дивіться другу половину моєї відповіді.
Regexident

Відповіді:


279

Оголошення бітових масок:

Як альтернативи присвоєння абсолютних значень ( 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 для отримання додаткової інформації про останню)

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

Звідси ORприведення двох значень робить наступне:

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

Побачте тонку різницю (і чому ваш поточний вираз "якщо", ймовірно, неправильний)?


@Abizern: Дякую! Думав, що це питання заслуговує на трохи більше пояснень, ніж було раніше.
Regexident

Так, але ви форматуєте двійкові значення як шістнадцяткові (перед ними 0x). Бітмаски працюють на бітовому рівні. Проста помилка, я впевнений, ви цього навіть не помітили. Але хтось може поглянути на це і неправильно припустити, що ви можете мати максимум 8 варіантів на перерахування, коли насправді ви можете мати максимум 32 різні варіанти. Виправлення: FileNotDownloaded = (0x1 << 0), // => %...00000001і т. Д.
Майкл Циммерман

1
Apple пропонує чудову пару макросів NS_ENUM та NS_OPTION для декларацій переліку та бітової маски. Використовуйте їх. Деякі хороші описи див. На сайті NSHipster.
uchuugaka

2
Цілком усвідомлюючи їх. ;) (Див. Відповідь Абізерна) У будь-якому разі, додано варіацію NS_OPTIONSдля повноти.
Regexident

1
Правильно. Я розумію, що ви маєте на увазі щодо переливів. Можливо, це має бути просто ((status & FileNotDownloaded) == FileNotDownloaded), тому для тегу можливі лише два результати.
uuuugaka

20

Хоча @Regexident дав чудову відповідь - я повинен згадати сучасний спосіб Objective-C декларування перелічених варіантів за допомогою NS_OPTIONS:

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

Подальші посилання:


Так, макроси NS_ENUM та NS_OPTION чудові.
uchuugaka

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

Це дозволить вам ефективно виконувати побітові АБО та І.


4
Стандартний спосіб визначення значень 1 << 0, 1 << 1, і 1 << 2т.д. Це прояснює ви працюєте з битами і масками.
Mike Weller

1
@AhmedAlHafoudh: Однак у статті не розглядається друга проблема OP: робота з бітовими масками (проти простого їх оголошення). Дивіться мою відповідь.
Regexident

1

Корисна функція, яку ви можете використовувати для перевірки бітової маски для поліпшення читабельності.

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

Більш суворе (bitmask & contains) == contains- це буде працювати навіть при нуліcontains
DJm00n
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.