Is there any way I can use an NSKeyedArchiver to encode an NSValue that contains a custom struct?. I have an NSDictionary which contains structs wrapped in NSValues and I want to archive this as part of a larger object graph but I receive the following error:
-[NSKeyedArchiver encodeValueOfObjCType:at:]: this archiver cannot encode structs
However, consider the following:
//Copy of the CGPoint declaration
struct MYPoint {
CGFloat x;
CGFloat y;
};
typedef struct MYPoint MYPoint;
CGPoint point = {1.0, 1.0};
MYPoint myPoint = {1.0, 1.0};
NSLog(#"CGPoint: %s", #encode(CGPoint)); //CGPoint: {CGPoint=ff}
NSLog(#"MYPoint: %s", #encode(MYPoint)); //MYPoint: {MYPoint=ff}
NSValue *CGPointValue = [NSValue valueWithBytes:&point objCType:#encode(CGPoint)];
NSData *CGPointData = [NSKeyedArchiver archivedDataWithRootObject:CGPointValue];
//NO ERROR
NSValue *MYPointValue = [NSValue valueWithBytes:&myPoint objCType:#encode(MYPoint)];
NSData *MYPointData = [NSKeyedArchiver archivedDataWithRootObject:MYPointValue];
//ERROR: -[NSKeyedArchiver encodeValueOfObjCType:at:]: this archiver cannot encode structs
Is it just the case that "this archiver cannot encode your structs" and thats the end of the story or is it possible to get the same behaviour as CGPoint but for custom structs?
I will probably just create a small custom object that wraps an NSValue and implements NSCoding to work around it, but I am curious about the contradiction shown in the code above with CGPoint and wonder if there is a way to extend NSValue to get the same behaviour.
There are issues with something to do with #encode and anonymous structs. Try changing the struct definition to:
typedef struct MYPoint {
CGFloat x;
CGFloat y;
} MYPoint;
If that doesn't work, you can wrap the struct using NSData:
[NSData dataWithBytes:&myPoint length:sizeof(MYPoint)];
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:#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:#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:#encode(char **)];
Here the address of myCString is passed (&myCString), so the address of the first character of the string is stored in theValue.
Important: The 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.
Related
I've an objective-C function like below:
- (NSValue *)foo:(NSString *)str {
NSValue* val = [NSValue valueWithPointer: str.UTF8String];
char* ptr = [val pointerValue];
return val;
}
It takes in a parameter NSString str. Inside the function, I get UTF8 representation of the the string which is a copy of characters of the string (i.e. the pointer for str and str.UT8String are different).
Now, I store this pointer information in NSValue and return this NSValue for further usage.
However, I've observed that str.UTF8String acts as a local variable to this function is a released as we come out of the function, thus leaving its pointer dangling.
Thus, when I try to use the pointer in NSValue to recreate the string, it either returns a nil or random value to me. Occasionally, it also returns true value to me.
How can I safetly save the pointer of str.UTF8String for further usage?
In the example
NSString *message = #"Hello";
message = #"World";
If message is just a pointer why don't I need to explicitly say whatever is in message is now equal to string or *message = #"World"; like in C?
DISCLAIMER
The discussion below gives a general idea on why you never dereferenciate a pointer to an object in Objective-C.
However, concerning the specific case of NSString literals, this is not what's happening in reality. While the structure described below is still sound and it may work that way, what's actually happening is that the space for a string literal is allocated at compile time, and you get its address back. This is true for string literals, since they are immutable and constant. For the sake of efficiency therefore each literal is allocated only once.
As a matter of fact
NSString * a = #"Hello";
NSString * b = #"Hello";
NSLog(#"%# %p", a, a); // Hello 0x1f4958
NSLog(#"%# %p", b, b); // Hello 0x1f4958
ORIGINAL ANSWER
Because it will be translated to
message = [[NSString alloc] initWithUTF8String:"Hello"]];
which will boil down to
message = objc_msgSend(objc_msgSend(objc_getClass("NSString"), #selector(alloc)), #selector(initWithUTF8String:), "Hello");
Now if we take a look to the signature of objc_msgSend
id objc_msgSend(id theReceiver, SEL theSelector, ...)
we see that the method returns an id type, which in Objective-C is the object type. But how is id actually defined?
typedef struct objc_object {
Class isa;
} *id;
id is defined as a pointer to an objc_object struct.
So in the end #"string" will translate in a function call that will produce a pointer to an object (i.e. an objc_object struct, if you prefer), which is exactly what you need to assign to message.
Bottom line, you assign pointers, not objects.
To better clarify the last concept consider this
NSMutableString * a = [NSMutableString stringWithString:#"hello"];
NSMutableString * b = a;
[a setString:#"hola"];
NSLog(#"%#", a); // "hola"
NSLog(#"%#", b); // "hola"
If you were assigning objects, b would have been a copy of a and any further modification of a wouldn't have affected b.
Instead what you get is a and b being two pointers to the same object in the heap.
a.H:
-(NSArray *) returnarray:(int) aa
{
unsigned char arry[1000]={"aa","vv","cc","cc","dd"......};
NSArray *tmpary=arry;
return tmpary;
}
a.c:
#include "a.H"
main (){
// how do I call returnarray function to get that array in main class
}
I need that array in main and I need to retain that array function in separate class.
Can someone please provide a code example to do this?
These lines:
unsigned char arry[1000]={"aa", "vv", "cc", "cc", "dd", ...};
NSArray *tmpary=arry;
Should instead be:
unsigned char arry[1000]={"aa", "vv", "cc", "cc", "dd", ...};
NSMutableArray * tmpary = [[NSMutableArray alloc] initWithCapacity: 1000];
for (i = 0; i < 1000; i++)
{
[tmpary addObject: [NSString stringWithCString: arry[i] encoding:NSASCIIStringEncoding]];
}
This is because a C-style array (that is, int arr[10]; for example) are not the same as actual NSArray objects, which are declared as above.
In fact, one has no idea what an NSArray actually is, other than what the methods available to you are, as defined in the documentation. This is in contrast to the C-style array, which you are guaranteed is just a contiguous chunk of memory just for you, big enough to hold the number of elements you requested.
C-style arrays are not NSArray's so your assignment of arry (the definition of which has some typos, at least the unsighned part) is not valid. In addition, you call arry an array of char, but you assign it an array of null-terminated strings.
In general you need to loop and add all the elements of the C-style array to the NSArray.
I'm not sure why you must do it in main. If you want a global you can do it by declaring a global in another file. That said, you CANNOT assign a plain C data array to an objective C NSArray, which is different in nature entirely.
Is there a way to make nullable struct in objective C like in C# you can use Nullable<T>?
I need a CGPoint to be null when there is no applicable value. I cannot allocate a random invalid value for this like (-5000, -5000) because all values are valid for this.
What if you define a CGPoint using CGPointMake(NAN, NAN) similar to CGRectNull? Surely with NAN's for coordinates, it's not still a valid point.
CGPoint is a struct and that has some different rules in objective-c than you might think. You should consider reading about structs in objective-c.
The way this is done most of the time is to wrap the struct in an object because that object can be set to null. NSValue will wrap a CGPoint.
NSValue * v = [NSValue valueWithPoint:CGPointMake(1,9)];
NSVAlue * vNull = [NSValue valueWithPointer:nil];
if([v objCType] == #encode(CGPoint)) printf("v is an CGPoint");
CGPoint is a enum, not an object. You can use CGPointZero, or you can wrap all of your points inside of NSValue, which are objects and can be nil.
There is also nothing stopping you creating your own struct based on CGPoint, similar to how C# 2 works.
struct NilableCGPoint { bool isNil; CGPoint point; }
Examples of use:
// No value (nil)
NilableCGPoint myNilablePoint.point = CGPointZero;
myPoint.isNil = YES;
// Value of (0,0)
NilableCGPoint myNilablePoint.point = CGPointZero;
myPoint.isNil = NO;
// Value of (100, 50)
NilableCGPoint myNilablePoint.point = CGPointMake(100, 50);
myPoint.isNil = NO;
I am trying to create a re-sizable array of CGPoints in objective-c. I've have looked into using NSMutableArray however it doesnt seem to allow resizing. Is there something else I can use?
thank you
Use an NSMutableArray, but just box your CGPoint structs in NSValue objects. Example:
CGPoint myPoint = {0,0};
CGPoint anotherPoint = {42, 69};
NSMutableArray * array = [NSMutableArray array];
[array addObject:[NSValue valueWithCGPoint:myPoint]];
[array addObject:[NSValue valueWithCGPoint:anotherPoint]];
CGPoint retrievedPoint = [[array objectAtIndex:0] CGPointValue];
Note that the +valueWithCGPoint: and CGPointValue methods are only available on the iPhone. However if you need this for the Mac, there are similar methods for dealing with NSPoints, and it's trivial to convert a CGPoint to an NSPoint (you can cast or use NSPointFromCGPoint()).
NSMutableArray is for objects. For plain old datatypes, use NSMutableData and good old pointer typecasts. It's a resizable unstructured memory buffer, just what you need for a vector of PODs. As for static type safety, Objective C does not give you any of that anyway.
EDIT:
Creating a mutable data object with an initial size n CGPoint structs:
NSMutableData *Data = [NSMutableData dataWithLength: n*sizeof(CGPoint)];
Placing a CGPoint pt into the buffer at the i-th position:
CGPoint pt;
NSRange r = {i*sizeof(CGPoint), sizeof(CGPoint)};
[Data replaceBytesInRange: r withBytes:&pt];
Retrieving a CGPoint from the i-th position into pt:
CGPoint pt;
NSRange r = {i*sizeof(CGPoint), sizeof(CGPoint)};
[Data getBytes: &pt range:r];
Growing the array by n objects:
[Data increaseLengthBy:n*sizeof(CGPoint)];
Hope that covers it. See the NSMutableData reference, and keep in mind that all NSData methods apply to it.