I'd like to learn bit masking. As far as I understand, it is means to store binary values of certain type into one variable.
If the above assumption is true, I figured I could do something like this:
typedef NSUInteger Traits;
enum
{
TraitsCharacterHonest = 0,
TraitsCharacterOptimistic = 1,
TraitsCharacterPolite = 4,
TraitsCharacterDevious = 8,
TraitsPhysicalTall = 16,
TraitsPhysicalBeautiful = 32,
TraitsPhysicalFat = 64,
TraitsPhysicalBigEyes = 128,
TraitsPhysicalRedHair = 256,
};
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (strong, nonatomic) NSString *name;
@property (assign, nonatomic) Traits *traits;
@end
Question 1 is, how do I assign more traits to one person?
Question 2 is, do I have to put ever increasing numbers to enum items, or is there a way to indicate this?
Ultimately I want to achieve something like this:
Person *john = [[Person alloc] init];
//here code that assigns john three traits: TraitsCharacterHonest,
//TraitsCharacterOptimistic and TraitsPhysicalBeautiful.
If I understand it correctly, the value of
john.traits should be 100011., reading from right and each place representing that particular enum value / trait..and 0 meaning not having it and 1 meaning having it.
Can you please advice on syntax and explain a particular aspect if needed?
I'd recommend changing a few things:
The enum values can be changed to be a one left-shifted. Makes it a little easier to write, in my opinion.
You don't need to typedef to NSUInteger, you can declare a enum type directly using typedef enum
.
And, as other people have mentioned, your property shouldn't be a pointer to a Traits type.
My code would look like this:
typedef enum
{
TraitsCharacterHonest = 1 << 0,
TraitsCharacterOptimistic = 1 << 1,
TraitsCharacterPolite = 1 << 2,
TraitsCharacterDevious = 1 << 3,
TraitsPhysicalTall = 1 << 4,
TraitsPhysicalBeautiful = 1 << 5,
TraitsPhysicalFat = 1 << 6,
TraitsPhysicalBigEyes = 1 << 7,
TraitsPhysicalRedHair = 1 << 8
} Traits;
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (strong, nonatomic) NSString *name;
@property (assign, nonatomic) Traits traits;
@end
Setting John's traits will look like this:
Person *john = [[Person alloc] init];
john.traits = TraitsCharacterHonest | TraitsCharacterOptimistic | TraitsPhysicalBeautiful;
However, while bit-fields are useful to learn, but they're a real pain to debug. If you want to go and print this character's traits now, you'll have to write code like this:
NSMutableString *result = [NSMutableString string];
if (self.traits & TraitsCharacterHonest)
{
[result appendString: @"Honest, "];
}
if (self.traits & TraitsCharacterOptimistic)
{
[result appendString: @"Optimistic, "];
}
if (self.traits & TraitsCharacterPolite)
{
[result appendString: @"Polite, "];
}
// etc...
Additionally, syntax for operations like removing a trait are confusing. You'll have to use &
and a NOT-ed constant,
// remove 'Tall' trait
john.traits = john.traits & ~TraitsPhysicalTall
If you can (and performance isn't too much of a issue), I'd prefer using a higher-level feature. Perhaps an NSSet with string constants? e.g.
__unused static NSString *TraitsCharacterHonest = @"TraitsCharacterHonest";
__unused static NSString *TraitsCharacterOptimistic = @"TraitsCharacterOptimistic";
__unused static NSString *TraitsCharacterPolite = @"TraitsCharacterPolite";
// etc...
@interface Person : NSObject
@property (strong, nonatomic) NSString *name;
@property (assign, nonatomic) NSMutableSet *traits;
@end
Then you can do:
// adding
[john.traits addObject: TraitsCharacterHonest];
// checking
[john.traits containsObject: TraitsCharacterHonest];
// removing
[john.traits removeObject: TraitsCharacterHonest];
Makes more sense to me. What's more, you can print the description of the traits directly with
NSLog(@"John's traits: %@", john.traits);
and you'll get reasonable output.
One issue that you can run into is that using bit masks to indicate membership within sets can be capped by the number of bits in the underlying data type. For instance an unsigned long of 32 bits has room only for 32 disjoint or different members. If you need to add a 33rd, you are out of luck unless you go to a 64 bit unsigned integer.
One workaround for this is to use an array of bytes. With this approach you have to specify your bit membership as two pieces of data, the offset to the byte and the bit mask to use for the specific bit.
I have also seen people use byte arrays for single membership so that rather than one bit used, the entire byte is used. It can be a waste of memory but then it may be that it is more flexible and useful and the amount of memory wasted is not a problem.
For using an array of bytes to hold the set of bits, you might consider using an unsigned long to represent the members of the set in which the least significant byte is the bit mask and the rest of the bytes are used as an unsigned 3 byte offset into the byte array. You would then do something like the following:
int getBitSet (unsigned char *bArray, unsigned long ulItem)
{
unsigned long ulByteOffset = ((ulItem >> 8) & 0x00ffffff);
unsigned char ucByteMask = (ulItem & 0x000000ff);
return (*(bArray + ulByteOffset) & ucByteMask);
}
int setBitSet (unsigned char *bArray, unsigned long ulItem, unsigned long ulNewValue)
{
unsigned char oldValue;
unsigned long ulByteOffset = ((ulItem >> 8) & 0x00ffffff);
unsigned char ucByteMask = (ulItem & 0x000000ff);
oldValue = *(bArray + ulByteOffset) & ucByteMask;
if (ulNewValue) {
*(bArray + ulByteOffset) |= ucByteMask; // set bit
} else {
*(bArray + ulByteOffset) &= ~ucByteMask; // clear bit
}
return oldValue;
}
You could then have a set of functions to get and set the bytes or you could use macros. With C++ you can create your own class for this functionality and provide various types of logical operations as well so that you can create sets of various kinds and then perform logical operations on the sets.
In iOS 6 or above, Mac OS X 10.8 and above
You can do:
typedef NS_OPTIONS(NSUInteger, Traits) {
TraitsCharacterHonest,
TraitsCharacterOptimistic,
TraitsCharacterPolite,
TraitsCharacterDevious,
TraitsPhysicalTall,
TraitsPhysicalBeautiful,
TraitsPhysicalFat,
TraitsPhysicalBigEyes,
TraitsPhysicalRedHair
};
For more info, refer to http://nshipster.com/ns_enum-ns_options/
Your major issue here is making traits
a pointer. Drop the pointer, and do it like you would in C:
john.traits |= TraitsCharacterOptimistic | TraitsCharacterOptimistic | TraitsCharacterOptimistic;
Remember that you only need pointers in a couple of situations in Objective-C:
NSObject
)int *
argument to a function to return count), in which case you take the adress of a local variable, and that pointer is not saved by the function.malloc
& friends).Otherwise, just use a stack-allocated primitive type, as you can do a lot of things with it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With