Getting a struct back from an NSValue object

Discussion in 'Mac Programming' started by RossOliver, Jul 8, 2008.

  1. RossOliver macrumors regular

    Joined:
    Nov 6, 2006
    #1
    Hey,

    I need to store C structures in an NSArray, so I wrap them in an NSValue object and then shove that object in:

    Code:
    typedef struct {...} MyStruct;
    
    NSString *key = @"whatever";
    	
    NSValue *myStructPtr = [NSValue value:&myStruct withObjCType:@encode( MyStruct )];
    	
    [myDictionary myStructPtr forKey:key];
    
    My question is how do I get them back out? Get the void pointer and cast it to the struct type then dereference? I would have thought the NSValue class would have a method to getValue withObjCType :confused:

    Thanks for your time,

    -Ross
     
  2. Enuratique macrumors 6502

    Joined:
    Apr 28, 2008
    #2
    Interesting concept you're trying here. For starters, the documentation mentions that the value: withObjCType method may be deprecated in the future and that you should use the valueWithBytes: withObjCType: method instead.

    But really, what I think you're trying to do is the valueWithPointer static method. The valueWithBytes will attempt to copy the contents of your pointer into the dictionary where as valueWithPointer will just store the pointer. The documentation for this method does make mention that you should not deallocate the memory pointed at by your pointer because if you pull it out of the dictionary and try to dereference it, it may no longer be a valid pointer. So unless you structs are defined in a method's local scope and you're doing all of your work with the dictionary in that method, then I think the structs will be taken off the stack when the method ends (and your locally defined structs fall out of scope). Thus I can see why you might try the valueWithBytes.

    The documentation also says for arbitrary pointers, NSData is a better suited class than NSValue. Which got me to thinking, you're just trying to store a struct in a dictionary for later use, right? There are ways to serialize primitives into an NSData object which you can than easily store into your dictionary and then deserialize when you need it back into a struct. Check out this documentation.
     
  3. RossOliver thread starter macrumors regular

    Joined:
    Nov 6, 2006
    #3
    The document you linked to seems to make sense, but unfortunately the serializing primitive data methods are deprecated:

    I looked at NSPropertyListSerialization and unfortunately it is bound to certain types:

    Is there another class that has superseded NSMutableData for primitive serialization?

    Cheers,

    -Ross
     
  4. Enuratique macrumors 6502

    Joined:
    Apr 28, 2008
    #4
    Hmm

    Try looking into the Archiving and Serialization Programming Guide. Of specific interest, look into Encoding and Decoding C Data Types and how that fits into the Apple's Serialization frameworks.

    Now, this may seem like a silly question, but is it really necessary to use a struct? Could you not define a very simple container class that inherits from NSObject instead and use it? Might save you a lot of trouble unless you need this struct to interoperate with an existing piece of code that is written in C/C++ and not Objective C.

    Happy hacking,

    Enuratique
     
  5. RossOliver thread starter macrumors regular

    Joined:
    Nov 6, 2006
    #5
    The structure simply contains two fields - I want to make my application as efficient as possible and the overhead of creating objects instead of structures would be quite significant I think - although there will be overhead in the serializing/de-serializing so I guess it's a lose-lose situation. Creating a whole new class for just two fields is a bit messy I feel though...

    I'll take a look at those documents and see if I can work anything out - thanks for your help :)

    -Ross
     
  6. Enuratique macrumors 6502

    Joined:
    Apr 28, 2008
    #6
    Eh, you'd be surprised at how efficient the compiler is at optimizing things. Yes, you're right trying to serialize/deserialize is a lose-lose situation. Can you elaborate a little bit more on what you're doing with these structs and why they need to be persisted? The one downside to the convenience of all these collection classes in the SDK is that they required objects - they don't play too nicely with C primitives... If you absolutely do not need to reference them by key (ergo, don't need a dictionary) then I'd just say go with old-school C arrays for persistence.

    There is Kazlib, a C implementation of a dictionary, but bringing in that library would probably be WAY overkill. Honesly, I think the quickest fix would be a simple container class... I wrote a very simple node object that only has two properties in it - a dictionary and an array. It's not as bad as you would think and it operates quickly enough.
     
  7. RossOliver thread starter macrumors regular

    Joined:
    Nov 6, 2006
    #7
    By messy I meant the source code would be messy - I'm a bit OCD that way...

    I don't want to give away any information on my program, but the structs are created after each continuous button press by the user and reference later, hence they must be indexed into a dictionary.

    I've written custom encoding/decoding methods using NSKeyedArchiver/NSKeyedUnarchiver and it seems to work well - thanks again for the reference :)

    -Ross
     
  8. Enuratique macrumors 6502

    Joined:
    Apr 28, 2008
    #8
    Cheers, glad to help. Sorry you had to go that route in the first place but sounds like performance isn't too much of a hit.
     
  9. theoknock macrumors newbie

    theoknock

    Joined:
    Aug 22, 2017
    #9

    Using Values
    An NSValue object is a simple container for a single C or Objective-C data item. It can hold any of the scalar types such as int, float, and char, as well as pointers, structures, and object ids. The purpose of this class is to allow items of such data types to be added to collection objects such as instances of NSArray or NSSet, which require their elements to be objects. NSValue objects are always immutable.

    To create an NSValue object with a particular data item, you provide a pointer to the item along with a C string describing the item’s type in Objective-C type encoding. You get this string using the @encode()compiler directive, which returns the platform-specific encoding for the given type (see Type Encodings for more information about @encode()and a list of type codes). For example, this code excerpt creates theValue containing an NSRange:

    NSRange myRange = {4, 10};
    NSValue *theValue = [NSValue valueWithBytes:&myRange objCType:mad:encode(NSRange)];
    The following example illustrates encoding a custom C structure.

    // assume ImaginaryNumber defined:
    typedef struct {
    float real;
    float imaginary;
    } ImaginaryNumber;


    ImaginaryNumber miNumber;
    miNumber.real = 1.1;
    miNumber.imaginary = 1.41;

    NSValue *miValue = [NSValue valueWithBytes: &miNumber
    withObjCType:mad:encode(ImaginaryNumber)];

    ImaginaryNumber miNumber2;
    [miValue getValue:&miNumber2];
    The type you specify must be of constant length. You cannot store C strings, variable-length arrays and structures, and other data types of indeterminate length in an NSValue—you should use NSString or NSData objects for these types. You can store a pointer to variable-length item in an NSValue object. The following code excerpt incorrectly attempts to place a C string directly into an NSValue object:

    /* INCORRECT! */
    char *myCString = "This is a string.";
    NSValue *theValue = [NSValue valueWithBytes:myCString withObjCType:mad:encode(char *)];
    In this code excerpt the contents of myCString are interpreted as a pointer to a char, so the first four bytes contained in the string are treated as a pointer (the actual number of bytes used may vary with the hardware architecture). That is, the sequence “This” is interpreted as a pointer value, which is unlikely to be a legal address. The correct way to store such a data item is to use an NSString object (if you need to contain the characters in an object), or to pass the address of its pointer, not the pointer itself:

    /* Correct. */
    char *myCString = "This is a string.";
    NSValue *theValue = [NSValue valueWithBytes:&myCString withObjCType:mad:encode(char **)];
    Here the address of myCString is passed (&myCString), so the address of the first character of the string is stored in theValue.

    NSValue object doesn’t copy the contents of the string, but the pointer itself. If you create an NSValue object with an allocated data item, don’t free the data’s memory while the NSValue object exists.



    NextPrevious
     
  10. mfram macrumors 65816

    Joined:
    Jan 23, 2010
    Location:
    San Diego, CA USA
    #10
    Uh, do you realize this thread is more than 9 years old?
     
  11. theoknock macrumors newbie

    theoknock

    Joined:
    Aug 22, 2017
    #11
    It's a valid answer to a question still asked. Why not complete it? Either way, if it's too old, then delete it.
     

Share This Page