Declarar y comprobar / comparar (máscara de bits) enumeraciones en Objective-C

79

Sabes que en Cocoa existe esto, por ejemplo puedes crear un UIViewy hacer:

view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

Tengo una costumbre UIViewcon varios estados, que he definido de enumesta manera:

enum DownloadViewStatus {
  FileNotDownloaded,
  FileDownloading,
  FileDownloaded
};

Para cada subvista creada, configuro su tag:subview1.tag = FileNotDownloaded;

Luego, tengo un configurador personalizado para el estado de la vista que hace lo siguiente:

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

Pero lo que estoy tratando de hacer es permitir esto:

subview1.tag = FileNotDownloaded | FileDownloaded;

Entonces mi subview1aparece en dos estados de mi vista. Actualmente, no aparece en ninguno de esos dos estados, ya que el |operador parece agregar los dos valores de enumeración.

¿Hay una manera de hacerlo?

thibaultcha
fuente
Tu se (subview.tag == viewStatus)ve mal para mí. Debería serlo ((subview.tag & viewStatus) != 0x0), a menos que solo desee verificar la coincidencia exacta. En cuyo caso, en primer lugar, no necesitaría una máscara de bits, sino una simple enumeración antigua. Vea la segunda mitad de mi respuesta.
Regexident

Respuestas:

279

Declaración de máscaras de bits:

Como alternativa a la asignación de valores absolutos ( 1, 2, 4, ...) se puede declarar máscaras de bits (cómo éstos se llaman) como este:

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

o usando macros NS_OPTIONS/ ObjC modernos NS_ENUM:

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

(consulte la respuesta de Abizern para obtener más información sobre este último)

El concepto de máscaras de bits es (normalmente) definir cada valor de enumeración con un solo conjunto de bits.

Por lo tanto OR, la obtención de dos valores hace lo siguiente:

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

que es equivalente a:

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

Comparación de máscaras de bits:

Una cosa a tener en cuenta al comparar las máscaras de bits:

Comprobando la igualdad exacta:

Supongamos que el estado se inicializa así:

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

Si desea verificar si status es igual FileNotDownloaded , puede usar:

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

que es equivalente a:

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

Comprobando "membresía":

Si desea verificar si statussimplemente contiene FileNotDownloaded , debe usar:

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

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

¿Ves la sutil diferencia (y por qué tu actual expresión "si" probablemente sea incorrecta)?

Regexident
fuente
@Abizern: ¡Gracias! Pensé que esta pregunta merecía un poco más de explicación de la que se había proporcionado anteriormente.
Regexident
Sí, pero está formateando los valores binarios como valores hexadecimales (precedidos por 0x). Las máscaras de bits funcionan a nivel de bits. Simple error, estoy seguro de que ni siquiera lo notó. Pero alguien podría mirar eso y asumir incorrectamente que puede tener un máximo de 8 opciones por enumeración, cuando en realidad puede tener un máximo de 32 opciones distintas. Corrección: FileNotDownloaded = (0x1 << 0), // => %...00000001etc.
Michael Zimmerman
1
Apple proporciona un maravilloso par de macros NS_ENUM y NS_OPTION para declaraciones de enum y máscara de bits. Usalos, usalos a ellos. Consulte el sitio NSHipster para obtener algunas buenas descripciones.
uchuugaka
2
Plenamente consciente de ellos. ;) (Vea la respuesta de Abizern) De todos modos, agregó una variación con NS_OPTIONSen aras de la integridad.
Regexident
1
Correcto. Entiendo a qué te refieres con los desbordamientos. Quizás debería ser simplemente ((status & FileNotDownloaded) == FileNotDownloaded) para etiquetar que solo hay dos resultados posibles.
uchuugaka
20

Si bien @Regexident ha proporcionado una respuesta excelente, debo mencionar la forma moderna de Objective-C de declarar opciones enumeradas con NS_OPTIONS:

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

Referencia adicional:

Abizern
fuente
Sí, las macros NS_ENUM y NS_OPTION son increíbles.
uchuugaka
1
enum DownloadViewStatus {
  FileNotDownloaded = 1,
  FileDownloading = 2,
  FileDowloaded = 4
};

Esto le permitirá realizar operaciones operativas y operaciones bit a bit de manera eficaz.

mah
fuente
4
La forma estándar de definir los valores es 1 << 0, 1 << 1, 1 << 2etc. Esto deja claro que está trabajando con los bits y máscaras.
Mike Weller
1
@AhmedAlHafoudh: Sin embargo, el artículo no aborda el segundo problema de OP: trabajar con máscaras de bits (en lugar de simplemente declararlas). Mira mi respuesta.
Regexident
1

Función útil que puede utilizar para comprobar la máscara de bits para mejorar la legibilidad.

BOOL bitmaskContains(NSUInteger bitmask, NSUInteger contains) {
    return (bitmask & contains) != 0;
}
Renetik
fuente
Más estricto (bitmask & contains) == contains- funcionará incluso con cerocontains
DJm00n