Deklarieren und Überprüfen / Vergleichen von (Bitmasken-) Aufzählungen in Objective-C

78

Sie wissen, dass es in Cocoa dieses Ding gibt, zum Beispiel können Sie ein erstellen UIViewund tun:

view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

Ich habe einen Brauch UIViewmit mehreren Zuständen, den ich folgendermaßen definiert enumhabe:

enum DownloadViewStatus {
  FileNotDownloaded,
  FileDownloading,
  FileDownloaded
};

Für jede erstellte Unteransicht setze ich Folgendes tag:subview1.tag = FileNotDownloaded;

Dann habe ich einen benutzerdefinierten Setter für den Ansichtsstatus, der Folgendes ausführt:

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

Aber was ich versuche zu tun , ist dies zuzulassen:

subview1.tag = FileNotDownloaded | FileDownloaded;

Also subview1taucht meine in zwei Zuständen meiner Ansicht nach auf. Derzeit wird es in keinem dieser beiden Zustände |angezeigt , da der Operator die beiden Aufzählungswerte zu addieren scheint.

Gibt es eine Möglichkeit, das zu tun?

Thibaultcha
quelle
Ihr (subview.tag == viewStatus)Aussehen falsch zu mir. Sollte sein ((subview.tag & viewStatus) != 0x0), es sei denn, Sie möchten nur die genaue Übereinstimmung überprüfen. In diesem Fall würden Sie überhaupt keine Bitmaske benötigen, sondern nur eine einfache alte Aufzählung. Siehe zweite Hälfte meiner Antwort.
Regexident

Antworten:

278

Bitmasken deklarieren:

Alternativ absolute Werte zuweisen ( 1, 2, 4, ...) können Sie erklären Bitmasken (wie diese genannt werden) wie folgt aus :

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

oder mit modernen ObjCs NS_OPTIONS/ NS_ENUMMakros:

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

( Weitere Informationen zu letzterem finden Sie in Abizerns Antwort. )

Das Konzept von Bitmasken besteht darin, (normalerweise) jeden Aufzählungswert mit einem einzelnen Bitsatz zu definieren.

Daher ORergibt zwei Werte Folgendes:

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

was äquivalent ist zu:

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

Bitmasken vergleichen:

Eine Sache, die Sie bei der Überprüfung gegen Bitmasken beachten sollten:

Überprüfung auf genaue Gleichheit:

Nehmen wir an, dass der Status folgendermaßen initialisiert wird:

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

Wenn Sie überprüfen möchten, ob status gleich ist FileNotDownloaded , können Sie Folgendes verwenden:

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

was äquivalent ist zu:

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

Überprüfung auf "Mitgliedschaft":

Wenn Sie überprüfen möchten, ob es statusnur enthält FileNotDownloaded , müssen Sie Folgendes verwenden:

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

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

Sehen Sie den subtilen Unterschied (und warum ist Ihr aktueller "Wenn" -Ausdruck wahrscheinlich falsch)?

Regexident
quelle
@Abizern: Danke! Ich dachte, diese Frage verdient etwas mehr Erklärung als zuvor.
Regexident
Ja, aber Sie formatieren die Binärwerte als Hexadezimalwerte (vorangestellt von 0x). Bitmasken arbeiten auf Bitebene. Einfacher Fehler, ich bin sicher, Sie haben es nicht einmal bemerkt. Aber jemand könnte sich das ansehen und fälschlicherweise annehmen, dass Sie maximal 8 Optionen pro Aufzählung haben können, wenn Sie tatsächlich maximal 32 verschiedene Optionen haben können. Korrektur: FileNotDownloaded = (0x1 << 0), // => %...00000001usw.
Michael Zimmerman
1
Apple bietet ein wunderbares Paar von Makros NS_ENUM und NS_OPTION für Enum- und Bitmaskendeklarationen. Benutze sie. Auf der NSHipster-Website finden Sie einige gute Beschreibungen.
Uchuugaka
2
Voll bewusst von ihnen. ;) (Siehe Abizerns Antwort) Der NS_OPTIONSVollständigkeit halber wurde eine Variation mit hinzugefügt .
Regexident
1
Richtig. Ich verstehe, was du mit Überläufen meinst. Vielleicht sollte es einfach sein ((status & FileNotDownloaded) == FileNotDownloaded), so dass nur zwei Ergebnisse möglich sind.
Uchuugaka
20

Während @Regexident eine hervorragende Antwort geliefert hat, muss ich die moderne Objective-C-Methode zur Deklaration von aufgezählten Optionen erwähnen mit NS_OPTIONS:

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

Weitere Referenz:

Abizern
quelle
Ja, die Makros NS_ENUM und NS_OPTION sind fantastisch.
Uchuugaka
1
enum DownloadViewStatus {
  FileNotDownloaded = 1,
  FileDownloading = 2,
  FileDowloaded = 4
};

Auf diese Weise können Sie bitweise ODERs und UNDs effektiv ausführen.

mah
quelle
4
Der üblicher Weg , die Werte zu definieren ist 1 << 0, 1 << 1, 1 << 2etc. Dies macht deutlich , Sie mit Bits und Masken arbeiten.
Mike Weller
1
@AhmedAlHafoudh: Der Artikel befasst sich jedoch nicht mit dem zweiten Problem von OP: der Arbeit mit Bitmasken (anstatt sie einfach zu deklarieren). Siehe meine Antwort.
Regexident
1

Nützliche Funktion, die Sie zur Bitmaskenprüfung verwenden können, um die Lesbarkeit zu verbessern.

BOOL bitmaskContains(NSUInteger bitmask, NSUInteger contains) {
    return (bitmask & contains) != 0;
}
Renetik
quelle
Strenger (bitmask & contains) == contains- es wird sogar mit Null contains
funktionieren