0

I want to convert NSValue to NSNumber. What is wrong with this code?

char value[4];
[myNSValue getValue:(void*)value];
NSNumber *number = [[NSNumber alloc] initWithBytes:(void*)value objCType:[myNSValue objCType]];
NSTimeInterval duration = [number intValue];

It causes a crash on the last line. What could happen here?

pro_metedor
  • 1,176
  • 10
  • 17
  • 2
    What are you trying to accomplish? NSNumber is not supposed to be used like this. –  Sep 25 '12 at 14:53
  • I want to get value from NSValue and store it in NSNumber to pass it easy then. Does NSNmber have different objCType argument then [NSValue objcType] returns? – pro_metedor Sep 25 '12 at 14:55
  • are you trying to convert char[4] to int? – Omar Abdelhafith Sep 25 '12 at 14:57
  • Correct me if I'm wrong, but isn't `value` a pointer? Then what you're trying to do on the fourth line is converting a pointer to an int. – TheAmateurProgrammer Sep 25 '12 at 14:59
  • @theAmateurProgrammer that's simply calling `intValue` on an NSNumber, why would it be a problem? (Also, you can convert a pointer to an int simply by casting, no magic here.) –  Sep 25 '12 at 15:01
  • Also, @pro_metedor what's the error message you are getting? –  Sep 25 '12 at 15:01
  • Im getting 'number doesn't respond to selector intValue'. This is curious one. – pro_metedor Sep 25 '12 at 15:03
  • 1
    When you're initialising your NSNumber with initWithBytes you're actually invoking an NSValue (NSNumber's parent class) initialiser, which returns an NSValue, thus it won't respond to intValue, as it doesn't implement it. – Alejandro Benito-Santos Sep 25 '12 at 15:15
  • @alex-unstable I'd be amazed if `-initWithBytes:ObjCType:` invoked on an `NSNumber` didn't return an `NSNumber`. It's the designated initialiser of `NSValue` which means probably all initialisers of `NSNumber` will call it. I'd regard that as a bug in Cocoa if it replaced the `NSNumber` allocated with an `NSValue` not compatible with `NSNumber`. – JeremyP Sep 25 '12 at 15:55
  • @alex-unstable Wow. Turns out it's true. You get an `NSConcreteValue` back. +1 (and for your answer). – JeremyP Sep 25 '12 at 16:02
  • @JeremyP That'd be the expected behaviour if NSNumber respected the two-stage creation pattern, but it doesn't seem to be the case... I wonder if this'd be enough to file a bug with Apple – Alejandro Benito-Santos Sep 25 '12 at 16:36
  • @alex-unstable I might raise the question on the Cocoa developer list. In my opinion it is a bug which is why I said I'd be amazed if the observed behaviour is what happens. – JeremyP Sep 27 '12 at 15:54

2 Answers2

2

As @ale0xB diagnosed in the comments above, NSNumber doesn't actually provide its own implementation of the -initWithBytes:objCType: method; so, when you invoke that selector, you're actually getting the implementation that comes from NSNumber's base class NSValue. There is no1 difference between [[NSNumber alloc] initWithBytes:foo objCType:bar] and [[NSValue alloc] initWithBytes:foo objCType:bar] — they both call the same method implementation, which releases the self it was given and returns a new object which is generally of type NSConcreteValue (an undocumented subclass of NSValue but not of NSNumber).

To be even clearer:

#import <assert.h>
#import <Foundation/Foundation.h>

int main()
{
    id x;

    x = [NSNumber numberWithInt:42];
    assert( [x isKindOfClass:[NSNumber class]] );
    assert( [x respondsToSelector:@selector(intValue)] );

    int fortytwo = 42;
    x = [[NSNumber alloc] initWithBytes:&fortytwo objCType:@encode(int)];
    assert(   [x isKindOfClass:[NSValue class]] );
    assert( ! [x isKindOfClass:[NSNumber class]] );  // yikes!
    assert( ! [x respondsToSelector:@selector(intValue)] );  // your particular problem
}

However, categories to the rescue! Martin Häcker has written a category NSNumber(CreatingFromArbitraryTypes) which can be adapted to do what you want. See the public-domain source code here.2 The basic idea is to special-case every possible type-encoding:

  • if you're trying to encode an "i", then dispatch to -numberWithInt:;
  • if you're trying to encode an "f", then dispatch to -numberWithFloat:;

and so on. This is tedious; but once you've written the code once, you can use the category from then on and simply call [NSNumber numberWithValue:myNSValue].

(1 – More or less. There's no difference significant in this context, I'd say.)

(2 – That code has a couple of bugs, especially in later revisions. See my patch here.)

Quuxplusone
  • 23,928
  • 8
  • 94
  • 159
  • Your answer is wrong and inaccurate, so I downvoted it. NSNumber **does** implement `initWith...` methods. Also, there's no need to use such category for a task this simple, you can always use `NSGetSizeAndAlignment`. – Alejandro Benito-Santos May 23 '13 at 12:38
  • Hm, I was reading the wrong docs, I guess. I've edited my answer to remove that paragraph, and also to link to the NSNumber Class Reference as a reliable source in support of the claim that NSNumber doesn't implement `-initWithBytes:objCType:`. – Quuxplusone May 23 '13 at 19:06
1

Please read my comment above for further explanation.

To achieve what you want, simply init an NSString with your (char *) and then invoke NSString's intValue. - Assuming you know the type, if not look at the comments below -

Cheers

  • Upvoted your comment above for correctness, but downvoted this answer because "init an NSString with your (char \*)" is definitely not what the OP wants. Suppose the original `myNSValue` contains the integer **55**. Then `char value[4]` will contain `"\x37\0\0\0"`, and `[[NSString stringWithUTF8String:value] intValue]` will be **7**, not **55**. – Quuxplusone May 21 '13 at 18:51
  • Hi, That's not the normal usage of stringWithUTF8String. It already supports UTF-8 by default, which means you could do something `[NSString stringWithUTF8String:"Hello \u0100 !"]` but not `[NSString stringWithUTF8String:"Hello \u0099 !"]` -> This doesn't even compile. Creating String buffers for conversions is a widely used technique in many other languages (like Java) which can be safely applied to Obj-C as well. Please reconsider your downvote, cheers. – Alejandro Benito-Santos May 22 '13 at 10:16
  • The problem here is not with `stringWithUTF8String`; it's with your understanding of what `-[NSValue getValue:value]` does. Do you understand that after `-[NSValue getValue:value]` the contents of `value` are `"\x37\0\0\0"` and not `"55"`? (And for your red-herring observation about `"\u0099"`, see [here](http://en.wikipedia.org/wiki/Unicode_control_characters). Again the code is not doing what you think it's doing.) – Quuxplusone May 22 '13 at 16:06
  • I DO understand that, who doesn't seem to understand is you. I also understand you're amazed of your latest discoveries and want to show them off, but that's not the point of this question. Obviously, by doing that change, you don't keep type information (it's gone with stringWithUTF8String (who told you to use that, anyways?) and, obviously again, it breaks the logics. Now, my answer assumed OP knew the type and he could format a string accordingly, which seems to be OK, mainly given he ACCEPTED the answer. btw: The link you posted is useless. It has nothing to do with control chars – Alejandro Benito-Santos May 23 '13 at 12:41