1

I have a simple object which has one NSNumber which is used to store some flags. I have a conienience getter method which in fact does:

[self.flags integerValue] & SomeConstantFlag

for a property@property (readonly, nonatomic, assign) BOOL someConstantFlag

and this works fine when accesing the underlying bool value like

model.someConstantFlag

but when I try to

id value = [model valueForKey:@"someConstantFlag"];

Then it returns a bad boolean representation e.g. NSNumber with value 2, 4 etc. Why is this happening when the declaration of the property is BOOL? Is there a "Pretty" way to overcome this issue?

Wrapping on the other hand works ok:

BOOL someBool = 42;
NSNumber* numberVal = @(someBool);
//The underlying is an __NSCFBoolean with the proper 0/1 val!
Qantas 94 Heavy
  • 15,750
  • 31
  • 68
  • 83
okipol
  • 1,197
  • 3
  • 11
  • 27
  • Try `BOOL someBool = [model boolForKey:@"someConstantFlag"];` – Akhilrajtr Apr 29 '14 at 08:54
  • May be this is because of `id` type ? BOOL is not an `id`..btw, both 2 and 4 is actually `YES` – Stas Apr 29 '14 at 08:55
  • This is not the case as I am using a generic method of handling creation of XML => i just send some properties and their coresponding names so that I can create the XML dynamically (the object-> to XML part). So I want to do this without any "type knowledge". The problem arises with the valueForKey: boxing -> the suggar code boxing works ok @(someBool). – okipol Apr 29 '14 at 08:56
  • `id` is a pointer to an object. but BOOL is not an object. – Rukshan Apr 29 '14 at 08:58
  • I made a "not so pretty" overcoming of this by making a definition `#define KVCCompilantBoolFromNumber(a) ((a) ? (1) : (0))` i Need this because I just create the inner string value from the id object itself. – okipol Apr 29 '14 at 09:06

3 Answers3

0

valueForKey always returns an Objective-C object, even if the property has scalar type.

From the documentation (emphasis mine):

The default implementations of valueForKey: and setValue:forKey: provide support for automatic object wrapping of the non-object data types, both scalars and structs.

Once valueForKey: has determined the specific accessor method or instance variable that is used to supply the value for the specified key, it examines the return type or the data type. If the value to be returned is not an object, an NSNumber or NSValue object is created for that value and returned in its place.

The return value of your method is BOOL, which is defined as

typedef signed char BOOL;

on OS X and on the 32-bit iOS platform. So what valueForKey returns is a NSNumber containing the result of

signed char val = [self.flags integerValue] & SomeConstantFlag;

and that can be in the range -128 .. 127.

To ensure that you get only YES or NO (aka 1 or 0) write your custom getter as:

-(BOOL)someConstantFlag
{
    return ([self.flags integerValue] & SomeConstantFlag) != 0;
}

Remark: On the 64-bit iOS platform (but not on 64-bit OS X), BOOL is defined as the C99 _Bool, which is a "proper" boolean type and can take only the value 0 or 1.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Problem with this is as before mentioned that the simple BOXING works and the `valueForKey:` does not work -> Does this mean that using `@(someBool)` is other that the `valueForKey:` (`@(someBool)` knows the type and uses the `__NSCFBoolean`?)?. Casting to BOOL when returning the value from the property accessor does not help! – okipol Apr 29 '14 at 09:11
  • @okipol: The compiler translates `@(someBool)` to `[NSNumber numberWithBool:…]`, that's why it works. The Key-Value Coding wrapper is not so smart. - Did you try my suggestion? – Martin R Apr 29 '14 at 09:15
  • Returning a value other than 0 or 1 from a method declared as BOOL is considered a serious bug by most people, with the expectation that eventually it will catch up with you and bite you in the arse. As it did. Martin's suggestion is a perfectly fine solution to your problem which you should accept instead of insisting on using buggy code. – gnasher729 Apr 29 '14 at 09:22
  • Well now in the updated post I see the awnser ;). Many Thanks! Please do LookUp my comment from some time ago attached to the main question for using `#define KVCCompilantBoolFromNumber(a) ((a) ? (1) : (0))` Which I thinnk is more reusable (reuse in manny getters) and acomplishes the same doesn't it? Thx for describing the difference betwen the sugar boxing and valueForKey. – okipol Apr 29 '14 at 09:34
  • @okipol: You are welcome! - `(a) != 0` is exactly equivalent to `((a) ? (1) : (0))`, so you can choose whatever you like more. - Btw, your "problem" does not occur on 64-bit iOS, I have updated the answer accordingly. – Martin R Apr 29 '14 at 09:50
0
NSNumber *value = @([model objectForKey:@"someConstantFlag"]);

BOOL boolVal = [value boolValue];
Rukshan
  • 7,902
  • 6
  • 43
  • 61
0

I think you should consider the following problems. Firstly, integerValue returns NSInteger which means if you support 64Bit architecture it will return int_64 not int_32, what is more in your code here

    [self.flags integerValue] & SomeConstantFlag

this does the following if flags is 00010 and somConstantFlags is 00001 the & of those will do something you probably does not expect because you will get value of 00000 which equals 0 or if they are 00011 and 00110 you will get 00010 which equals 2. So that is why when you call valueForKey you get 2 or 4 or something else depending on your flags :) What is more in objective-C everything different then 0 is YES.

Try reconsidering your bit logic :). See The following example

    enum 
    {
    kWhite   = 0,
    kBlue    = 1 << 0,
    kRed     = 1 << 1,
    kYellow  = 1 << 2,
    kBrown   = 1 << 3,
    };
    typedef char ColorType;

and in your setter check the following

ColorType pinkColor = kWhite | kRed;
if (pinkColor & (kWhite | kBlue | kRed | kYellow)) {
// any of the flags has been set
}

The flags kWhite, kBlue, kRed and kYellow have been set.

However, kBrown has not been set.

  • Thx for the anwser. But the logic I have is what I want -> In Your example when I get `0` then my property says that the flag is not set and when i get `2` then the property says that the specific flag is set. I use `flags` to store all of the flags in SQL lite DB -> one entry not nFlag entries. The only problem i am having is that even though the property is `BOOL` I get a wraped value as You have described (e.g. 2) but I want it to be 1 or 0 just like the @() would wrap it. – okipol Apr 29 '14 at 09:21
  • try [[model valueForKey:@"someConstantFlag"] boolValue] – Gandi Pirkov 愛 Apr 29 '14 at 09:30