Comment les champs de bits doivent-ils être déclarés et utilisés dans Swift?
Déclarer une énumération comme celle-ci fonctionne, mais la compilation de OR 2 valeurs échoue:
enum MyEnum: Int
{
case One = 0x01
case Two = 0x02
case Four = 0x04
case Eight = 0x08
}
// This works as expected
let m1: MyEnum = .One
// Compiler error: "Could not find an overload for '|' that accepts the supplied arguments"
let combined: MyEnum = MyEnum.One | MyEnum.Four
J'ai examiné la manière dont Swift importe les types de énumération Foundation, et ce, en définissant une variable struct
conforme au protocole RawOptionSet
:
struct NSCalendarUnit : RawOptionSet {
init(_ value: UInt)
var value: UInt
static var CalendarUnitEra: NSCalendarUnit { get }
static var CalendarUnitYear: NSCalendarUnit { get }
// ...
}
Et le protocole RawOptionSet
est:
protocol RawOptionSet : LogicValue, Equatable {
class func fromMask(raw: Self.RawType) -> Self
}
Cependant, il n'y a pas de documentation sur ce protocole et je n'arrive pas à comprendre comment le mettre en œuvre moi-même. De plus, il n’est pas clair si c’est la manière officielle de Swift d’implémenter les champs de bits ou si c’est seulement ainsi que le pont Objective-C les représente.
Vous pouvez créer une struct
conforme au protocole RawOptionSet
et vous pourrez l'utiliser comme le type intégré enum
mais avec la fonctionnalité de masque de bits également. La réponse ci-dessous montre comment: Enumérations de masque binaire de style NS_OPTIONS de Swift .
Ils ont montré comment faire cela dans l'une des vidéos de WWDC.
let combined = MyEnum.One.toRaw() | MyEnum.Four.toRaw()
Notez que combined
sera de type Int
et obtiendra une erreur de compilation si vous spécifiez let combined: MyEnum
. En effet, il n'y a pas de valeur enum pour 0x05
qui est le résultat de l'expression.
Depuis Swift 2, une nouvelle solution a été ajoutée en tant qu '"ensemble d'options brutes" ( voir: Documentation ), qui est essentiellement identique à ma réponse d'origine, mais utilisant des structures autorisant des valeurs arbitraires.
Voici la question originale réécrite en tant que OptionSet
:
struct MyOptions: OptionSet
{
let rawValue: UInt8
static let One = MyOptions(rawValue: 0x01)
static let Two = MyOptions(rawValue: 0x02)
static let Four = MyOptions(rawValue: 0x04)
static let Eight = MyOptions(rawValue: 0x08)
}
let m1 : MyOptions = .One
let combined : MyOptions = [MyOptions.One, MyOptions.Four]
La combinaison avec de nouvelles valeurs peut être effectuée exactement comme les opérations Set
(d'où la partie OptionSet), .union
, de même:
m1.union(.Four).rawValue // Produces 5
Identique à One | Four
dans son équivalent-C. Comme pour One & Mask != 0
, peut être spécifié comme une intersection non vide
// Equivalent of A & B != 0
if !m1.intersection(combined).isEmpty
{
// m1 belongs is in combined
}
Curieusement, la plupart des énumérations au niveau des bits de style C ont été converties en leur équivalent OptionSet
sur Swift 3, mais Calendar.Compontents
supprime un Set<Enum>
:
let compontentKeys : Set<Calendar.Component> = [.day, .month, .year]
Alors que la NSCalendarUnit
originale était un enum. Les deux approches sont donc utilisables (la réponse d'origine reste donc valide)
Je pense que la meilleure chose à faire est simplement d'éviter la syntaxe de masque de bits jusqu'à ce que les développeurs de Swift découvrent un meilleur moyen.
La plupart du temps, le problème peut être résolu en utilisant enum
et et Set
enum Options
{
case A, B, C, D
}
var options = Set<Options>(arrayLiteral: .A, .D)
Un et check (options & .A
) pourraient être définis comme suit:
options.contains(.A)
Ou pour plusieurs "drapeaux" pourraient être:
options.isSupersetOf(Set<Options>(arrayLiteral: .A, .D))
Ajout de nouveaux drapeaux (options |= .C
):
options.insert(.C)
Cela permet également d’utiliser toutes les nouveautés avec des énumérations: types personnalisés, correspondance de modèle avec boîtier de commutateur, etc.
Bien sûr, cela n’a pas l’efficacité des opérations au niveau des bits, ni serait compatible avec les choses de bas niveau (telles que l’envoi de commandes Bluetooth), mais il est utile pour les éléments de l’UI que la surcharge de l’UI soit supérieure au coût des opérations Set.
Je pense que certaines des réponses ici sont dépassées avec des solutions trop compliquées? Cela fonctionne bien pour moi ..
enum MyEnum: Int {
case One = 0
case Two = 1
case Three = 2
case Four = 4
case Five = 8
case Six = 16
}
let enumCombined = MyEnum.Five.rawValue | MyEnum.Six.rawValue
if enumCombined & MyEnum.Six.rawValue != 0 {
println("yay") // prints
}
if enumCombined & MyEnum.Five.rawValue != 0 {
println("yay again") // prints
}
if enumCombined & MyEnum.Two.rawValue != 0 {
println("shouldn't print") // doesn't print
}
Si vous n'avez pas besoin d'interagir avec Objective-C et que vous voulez juste le syntax des masques de bits dans Swift, j'ai écrit une simple "bibliothèque" appelée BitwiseOptions qui peut le faire avec des énumérations Swift normales, par exemple:
enum Animal: BitwiseOptionsType {
case Chicken
case Cow
case Goat
static let allOptions = [.Chicken, .Cow, .Goat]
}
var animals = Animal.Chicken | Animal.Goat
animals ^= .Goat
if animals & .Chicken == .Chicken {
println("Chick-Fil-A!")
}
etc. Aucun bit réel n'est retourné ici. Ce sont des opérations définies sur des valeurs opaques. Vous pouvez trouver le Gist ici .
Le très célèbre "NSHipster" de Mattt a une description détaillée détaillée de la RawOptionsSetType
: http://nshipster.com/rawoptionsettype/
Il comprend un Xcode pratique coupé:
struct <# Options #> : RawOptionSetType, BooleanType {
private var value: UInt = 0
init(_ value: UInt) { self.value = value }
var boolValue: Bool { return value != 0 }
static func fromMask(raw: UInt) -> <# Options #> { return self(raw) }
static func fromRaw(raw: UInt) -> <# Options #>? { return self(raw) }
func toRaw() -> UInt { return value }
static var allZeros: <# Options #> { return self(0) }
static func convertFromNilLiteral() -> <# Options #> { return self(0) }
static var None: <# Options #> { return self(0b0000) }
static var <# Option #>: <# Options #> { return self(0b0001) }
// ...
}
Vous devez utiliser .toRaw () après chaque membre:
let combined: Int = MyEnum.One.toRaw() | MyEnum.Four.toRaw()
marchera. Parce qu’en réalité, vous essayez simplement d’affecter "Un" qui est un type MyEnum, not un entier. Comme la documentation d'Apple dit:
«Contrairement à C et Objective-C, les membres de l'énumération Swift ne se voient pas attribuer une valeur entière par défaut lors de leur création. Dans l'exemple CompassPoints, Nord, Sud, Est et Ouest ne sont pas implicitement égaux à 0, 1, 2 et 3. Au lieu de cela, les différents membres de l'énumération sont des valeurs à part entière à part entière, avec un type explicitement défini de CompassPoint. ”
vous devez donc utiliser des valeurs brutes si vous voulez que les membres représentent un autre type, comme décrit ici :
Les membres de l'énumération peuvent être pré-remplis avec des valeurs par défaut (appelées valeurs brutes), qui sont toutes du même type. La valeur brute d'un membre d'énumération particulier est toujours la même. Les valeurs brutes peuvent être des chaînes, des caractères ou n’importe quel type de nombre entier ou à virgule flottante. Chaque valeur brute doit être unique dans sa déclaration d’énumération. Lorsque des entiers sont utilisés pour des valeurs brutes, ils s'incrémentent automatiquement si aucune valeur n'est spécifiée pour certains membres de l'énumération. Accédez à la valeur brute d'un membre d'énumération avec sa méthode toRaw.
Je suppose que quelque chose comme ceci est la façon dont ils modélisent les options enum dans Foundation:
struct TestOptions: RawOptionSet {
// conform to RawOptionSet
static func fromMask(raw: UInt) -> TestOptions {
return TestOptions(raw)
}
// conform to LogicValue
func getLogicValue() -> Bool {
if contains([1, 2, 4], value) {
return true
}
return false
}
// conform to RawRepresentable
static func fromRaw(raw: UInt) -> TestOptions? {
if contains([1, 2, 4], raw) {
return TestOptions(raw)
}
return nil
}
func toRaw() -> UInt {
return value
}
// options and value
var value: UInt
init(_ value: UInt) {
self.value = value
}
static var OptionOne: TestOptions {
return TestOptions(1)
}
static var OptionTwo: TestOptions {
return TestOptions(2)
}
static var OptionThree: TestOptions {
return TestOptions(4)
}
}
let myOptions = TestOptions.OptionOne | TestOptions.OptionThree
println("myOptions: \(myOptions.toRaw())")
if (myOptions & TestOptions.OptionOne) {
println("OPTION ONE is in there")
} else {
println("nope, no ONE")
}
if (myOptions & TestOptions.OptionTwo) {
println("OPTION TWO is in there")
} else {
println("nope, no TWO")
}
if (myOptions & TestOptions.OptionThree) {
println("OPTION THREE is in there")
} else {
println("nope, no THREE")
}
let nextOptions = myOptions | TestOptions.OptionTwo
println("options: \(nextOptions.toRaw())")
if (nextOptions & TestOptions.OptionOne) {
println("OPTION ONE is in there")
} else {
println("nope, no ONE")
}
if (nextOptions & TestOptions.OptionTwo) {
println("OPTION TWO is in there")
} else {
println("nope, no TWO")
}
if (nextOptions & TestOptions.OptionThree) {
println("OPTION THREE is in there")
} else {
println("nope, no THREE")
}
... où myOptions
et nextOptions
sont de type TestOptions - Je ne sais pas exactement comment fromMask()
et getLogicValue()
sont censés agir ici (j'ai juste pris quelques suppositions optimales), peut-être que quelqu'un pourrait le comprendre et le résoudre?
Si vous voulez un bitfield dans Swift, alors enum est la mauvaise façon. Mieux vaut juste faire comme ça
class MyBits {
static let One = 0x01
static let Two = 0x02
static let Four = 0x04
static let Eight = 0x08
}
let m1 = MyBits.One
let combined = MyBits.One | MyBits.Four
Vous n'avez pas vraiment besoin du wrapper class/static, mais je l'inclue comme une sorte de pseudo espace de noms.
J'utilise ce qui suit, j'ai besoin des deux valeurs que je peux obtenir, rawValue pour les tableaux d'indexation et valeur pour les indicateurs.
enum MyEnum: Int {
case one
case two
case four
case eight
var value: UInt8 {
return UInt8(1 << self.rawValue)
}
}
let flags: UInt8 = MyEnum.one.value ^ MyEnum.eight.value
(flags & MyEnum.eight.value) > 0 // true
(flags & MyEnum.four.value) > 0 // false
(flags & MyEnum.two.value) > 0 // false
(flags & MyEnum.one.value) > 0 // true
MyEnum.eight.rawValue // 3
MyEnum.four.rawValue // 2
Effectuez une opération bit à bit en utilisant une valeur brute, puis créez un nouvel objet enum en utilisant le résultat.
let mask = UIViewAutoresizing(rawValue: UIViewAutoresizing.FlexibleWidth.rawValue|UIViewAutoresizing.FlexibleHeight.rawValue)
self.view.autoresizingMask = mask
Voici quelque chose que j'ai mis en place pour essayer de créer une énumération Swift qui ressemble dans une certaine mesure à une énumération de type drapeaux C #. Mais je viens juste d'apprendre Swift, donc cela ne devrait être considéré que comme un code de "preuve de concept".
/// This EnumBitFlags protocol can be applied to a Swift enum definition, providing a small amount
/// of compatibility with the flags-style enums available in C#.
///
/// The enum should be defined as based on UInt, and enum values should be defined that are powers
/// of two (1, 2, 4, 8, ...). The value zero, if defined, should only be used to indicate a lack of
/// data or an error situation.
///
/// Note that with C# the enum may contain a value that does not correspond to the defined enum
/// constants. This is not possible with Swift, it enforces that only valid values can be set.
public protocol EnumBitFlags : RawRepresentable, BitwiseOperations {
var rawValue : UInt { get } // This provided automatically by enum
static func createNew(_ rawValue : UInt) -> Self // Must be defined as some boiler-plate code
}
/// Extension methods for enums that implement the EnumBitFlags protocol.
public extension EnumBitFlags {
// Implement protocol BitwiseOperations. But note that some of these operators, especially ~,
// will almost certainly result in an invalid (nil) enum object, resulting in a crash.
public static func & (leftSide: Self, rightSide: Self) -> Self {
return self.createNew(leftSide.rawValue & rightSide.rawValue)
}
public static func | (leftSide: Self, rightSide: Self) -> Self {
return self.createNew(leftSide.rawValue | rightSide.rawValue)
}
public static func ^ (leftSide: Self, rightSide: Self) -> Self {
return self.createNew(leftSide.rawValue ^ rightSide.rawValue)
}
public static prefix func ~ (x: Self) -> Self {
return self.createNew(~x.rawValue)
}
public static var allZeros: Self {
get {
return self.createNew(0)
}
}
// Method hasFlag() for compatibility with C#
func hasFlag<T : EnumBitFlags>(_ flagToTest : T) -> Bool {
return (self.rawValue & flagToTest.rawValue) != 0
}
}
Cela montre comment il peut être utilisé:
class TestEnumBitFlags {
// Flags-style enum specifying where to write the log messages
public enum LogDestination : UInt, EnumBitFlags {
case none = 0 // Error condition
case systemOutput = 0b01 // Logging messages written to system output file
case sdCard = 0b10 // Logging messages written to SD card (or similar storage)
case both = 0b11 // Both of the above options
// Implement EnumBitFlags protocol
public static func createNew(_ rawValue : UInt) -> LogDestination {
return LogDestination(rawValue: rawValue)!
}
}
private var _logDestination : LogDestination = .none
private var _anotherEnum : LogDestination = .none
func doTest() {
_logDestination = .systemOutput
assert(_logDestination.hasFlag(LogDestination.systemOutput))
assert(!_logDestination.hasFlag(LogDestination.sdCard))
_anotherEnum = _logDestination
assert(_logDestination == _anotherEnum)
_logDestination = .systemOutput | .sdCard
assert(_logDestination.hasFlag(LogDestination.systemOutput) &&
_logDestination.hasFlag(LogDestination.sdCard))
/* don't do this, it results in a crash
_logDestination = _logDestination & ~.systemOutput
assert(_logDestination == .sdCard)
*/
_logDestination = .sdCard
_logDestination |= .systemOutput
assert(_logDestination == .both)
}
}
Les suggestions d'amélioration sont les bienvenues.
EDIT: J'ai abandonné cette technique moi-même et je ne peux donc plus la recommander.
Le gros problème est que Swift exige que rawValue corresponde à l’une des valeurs enum définies. Cela n’est pas grave s’il n’ya que 2, 3 ou peut-être même 4 bits d’indicateur. Il suffit de définir toutes les valeurs de combinaison pour rendre Swift heureux. Mais pour 5 bits de drapeau ou plus, cela devient totalement fou.
Je laisserai ce message au cas où quelqu'un le jugerait utile, ou peut-être comme avertissement de ne pas le faire.
Ma solution actuelle à cette situation est basée sur l'utilisation d'une structure plutôt que d'une énumération, ainsi que d'un protocole et de méthodes d'extension. Cela fonctionne beaucoup mieux. Peut-être que je le posterai un jour quand je serai plus sûr que ça ne va pas aussi se retourner contre moi.
Cela a fonctionné pour moi.
1 << 3 // 1000
enum Collision: Int {
case Enemy, Projectile, Debris, Ground
func bitmask() -> UInt32 {
return 1 << self.rawValue
}
}