I'm trying to save all the variables in my class into NSUserDefaults using objc/runtime. And below is the code I'm using.
NSUInteger count;
Ivar *iVars = class_copyIvarList([self class], &count);
for (NSUInteger i=0; i<count; i++)
{
Ivar var = iVars[i];
NSString *varName = [NSString stringWithCString:ivar_getName(var) encoding:NSUTF8StringEncoding];
NSString *varType = [NSString stringWithCString:ivar_getTypeEncoding(var) encoding:NSUTF8StringEncoding];
if([varType hasPrefix:#"["])
{
NSLog(#"Array");
id var1 = [_manager valueForKey:varName];
NSLog(#"--- %#", var1);
NSData *data = [NSData dataWithBytes:&([_manager valueForKey:varName]) length:sizeof([_manager valueForKey:varName])]
[[NSUserDefaults standardUserDefaults] setObject:[_manager valueForKey:varName] forKey:varName];
}
else
{
NSLog(#"NonArray");
NSLog(#"--- %#", [_manager valueForKey:varName]);
[[NSUserDefaults standardUserDefaults] setObject:[_manager valueForKey:varName] forKey:varName];
}
}
free(iVars);
The problem is that, when there are only primitive datatypes, the above code works just fine. But, when I try to access a array variable like int[], or float[], it gets crashed with SIGABRT. it is not showing any other messages.
valueForKey doesn't return any values for C arrays.
If anybody know how to load values for C-arrays in runtime, please help.
Thanks in advance,
Suran
Unless you always provide a paired length method, your program will never know the length of the array returned. So... you will need to do some work someplace to accomplish this without a crash.
If I really wanted to do what you're doing, I would make the class itself create the array, providing NSData. If this is common, you may want to use a convention:
- (int*)pixelBuffer;
- (NSData *)pixelBufferForSerialization; // << returns a deep copy of
// self.pixelBuffer as an
// NSData instance.
So your above implementation would see that the property defines a scalar array, and then request NSData * data = obj.pixelBufferForSerialization; instead of trying to produce the data itself.
Update
It's best to let the class do it. Here's how to create NSData using such an array:
#interface DataManager : NSObject
{
#private
int* things;
size_t nThings;
}
- (int*)things;
- (NSData *)thingsAsNSData;
#end
#implementation DataManager
- (int*)things
{
return things;
}
- (NSData *)thingsAsNSData
{
// note: you may need to choose an endianness for serialization
if (0 == nThings) return [NSData data];w
return [NSData dataWithBytes:things length:nThings * sizeof(things[0])];
}
#end
Again - you want the class to create the data because it knows its own structure best.
Related
I am studying this wavefront obj loader example: https://github.com/jlamarche/iOS-OpenGLES-Stuff/tree/master/Wavefront%20OBJ%20Loader
And in order to improve object loading speed - I decided to encode loaded objects as NSValue and then store in Core data. Later, when corresponding object needs to be shown again - I fetch from database necessary nsvalue and unarchieve it.
It's all working, but problem is - it's not as fast I hoped it would be.
One of the reasons - because, in that example are used struct objects.
I was not successful to encode/decode them, so I used NSMutableArray, in which I write all struct data, and later - I iterate through it to put back values to struct object.
For example, there is struct:
typedef struct {
GLfloat x;
GLfloat y;
GLfloat z;
} Vertex3D;
It is then defined:
typedef Vertex3D Vector3D;
But in OpenGLWaveFrontObject class it is used like this:
Vector3D *vertexNormals;
how can I encode/decode struct like this?
I've tried like this:
[coder encodeObject:[NSValue value:&vertexNormals withObjCType:#encode(Vector3D)] forKey:#"vertexNormals"];
or like this:
[coder encodeObject:[NSValue value:&vertexNormals withObjCType:#encode(Vector3D[30])] forKey:#"vertexNormals"];
but its not working - if successfully encoded, then when decoding - values are incorrect.
example, how I put back in array of struct necessary values:
vertices = malloc(sizeof(Vertex3D) * [verticesArray count]);
for(int i = 0; i < [verticesArray count]; i++)
{
vertices[i].x = [[[verticesArray objectAtIndex:i] objectAtIndex:0] floatValue];
vertices[i].y = [[[verticesArray objectAtIndex:i] objectAtIndex:1] floatValue];
vertices[i].z = [[[verticesArray objectAtIndex:i] objectAtIndex:2] floatValue];
}
this works, but I have few more struct arrays and in case arrays are big - this becomes a big issue.
EDIT:
Using Amin Negm-Awad provided answer:
If I simply change NSArchiver to NSKeyedArchiver - it throws error:
[NSKeyedArchiver encodeArrayOfObjCType:count:at:]: unsupported type "{?=fff}]" for array encoding'
When I try:
NSValue *encapsulated = [NSValue valueWithBytes:vertexNormals objCType:#encode(Vertex3D[3])];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:encapsulated];
[coder encodeObject:data forKey:#"vertexNormals"];
And vertexNormals were created:
vertexNormals = calloc(3, sizeof(Vector3D));
and filled with similar data You provided.
EDIT 2:
Using updated answer which Amin Negm-Awad provided,
I was successfully able to store struct object as NSData and encode it, and after that - decode it. It works!
example, so that it might help some one else too:
//Encode
NSData *verticesData = [NSData dataWithBytes:vertices length:numberOfVertices * sizeof(Vector3D)];
[coder encodeObject:verticesData forKey:#"vertices"];
//Decode
vertices = malloc(sizeof(Vertex3D) * numberOfVertices);
NSData *verticesData = [decoder decodeObjectForKey:#"vertices"];
[verticesData getBytes:vertices length:sizeof(Vertex3D) * numberOfVertices];
where Vertex3D was a struct:
typedef struct {
GLfloat x;
GLfloat y;
GLfloat z;
} Vertex3D;
and used as a struct array (I don't know how it is really called):
Vertex3D *vertices;
Unfortunately I was not able to store texture data. I can encode and decode, but decoded data are always randomly bizarre.
I am declaring it in this way:
GLfloat *textureCoords;
And I Encoded/Decoded in this way:
//Encode
NSData *textureCoordsData = [NSData dataWithBytes:textureCoords length:valuesPerCoord * numberOfVertices];
[coder encodeObject:textureCoordsData forKey:#"textureCoords"];
//Decode
textureCoords = malloc(sizeof(GLfloat) * valuesPerCoord * numberOfVertices);
NSData *textureCoordsData = [decoder decodeObjectForKey:#"textureCoords"];
[textureCoordsData getBytes:textureCoords length:valuesPerCoord * numberOfVertices];
What could be the problem?
First of all: If the array has variable length, it is simply not possible to use NSValue. This is documented:
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.
So you should really take into account to use NSData instead.
But, if you want to use NSValue (because you have a constant-length array):
Both of your approaches has an extra indirection. The first one has the wrong type, too. What should work:
// Create some data
Vertex3D *cArray = malloc(30 * sizeof(Vertex3D));
NSInteger i;
for (i=0; i<30; i++)
{
cArray[i].x = i;
cArray[i].y = 9811;
cArray[i].z = 29-i;
}
NSLog(#"%p", cArray);
// Encapsulate that in a value
// Have a look at the parameters
NSValue *encapsulated = [NSValue valueWithBytes:cArray objCType:#encode(Vertex3D[30])];
// Put it through a coder and store the coded data on disk
NSData *data = [NSArchiver archivedDataWithRootObject:encapsulated];
[data writeToFile:[#"~/Desktop/valueTest" stringByExpandingTildeInPath] atomically:YES];
// Out is done here
// from disk
data = [NSData dataWithContentsOfFile:[#"~/Desktop/valueTest" stringByExpandingTildeInPath]];
encapsulated = [NSUnarchiver unarchiveObjectWithData:data];
Vertex3D *cArray2 = malloc(30 * sizeof(Vertex3D));
[encapsulated getValue:cArray2];
for (i=0; i<30; i++)
{
NSLog(#"%f %f %f", cArray[i].x, cArray[i].y, cArray[i].z);
}
NSLog(#"%p", cArray2);
This works in my test code
Update because of the problem with keyed achieving:
To store the c array in an instance of NSData instead of an instance of NSValue do this instead
After
NSLog(#"%p", cArray);
change the code to
// Convert to an NSData instance
NSData *data = [NSData dataWithBytes:cArray length:lengthInBytes];
[data writeToFile:[#"~/Desktop/valueTest" stringByExpandingTildeInPath] atomically:YES];
// Out is done here
// from disk
data = [NSData dataWithContentsOfFile:[#"~/Desktop/valueTest" stringByExpandingTildeInPath]];
Vertex3D *cArray2 = malloc(lengthInBytes);
[data getBytes:cArray2 length:lengthInBytes];
until
for (i=0; i<30; i++)
In one class, I define an NSMutableArray with getters and setters:
#interface ArrayClass : NSObject {
NSMutableArray *array;
}
#property (nonatomic, strong) NSMutableArray *array;
#end
Then within the implementation file, I alloc init the mutable array:
#import "ImageUploader.h"
#implementation ArrayClass
#synthesize array;
- (id)init {
self = [super init];
if (self != nil) {
NSLog(#"ArrayClass inited");
array = [[NSMutableArray alloc] init];
}
return self;
}
#end
Then I initialize an instance of this class from another class:
ArrayClass *arrayClass = [[ArrayClass alloc] init];
[arrayClass.array addObject:image];
NSUInteger count = [arrayClass.array count];
NSLog(#"%#", count);
But when I try to add an object to the mutable array, the app crashes and Xcode 4.3 shows:
Removing the addObject call makes the app run fine. What am I doing wrong that would cause the app to crash?
This is wrong:
NSUInteger count = [arrayClass.array count];
NSLog(#"%#", count);
You want:
NSLog(#"%u", count);
%# is used to specify that the argument is an object. However, an NSUInteger is a primitive value, not an object. You use %u for unsigned ints.
try:
NSLog(#"%i", count);
NSUInteger return an INT not an object address
NSLog(#"%#", count);
is wrong, use:
NSLog(#"%i", count);
%# in the format statement expects and must be an object, NSUInteger is in int, not an object.
You are using an %# format specifier, which is for Cocoa objects only, for an NSUInteger, which is a typedef on an ordinary unsigned int. Use %d, %i or %u instead.
It looks to me like it's crashing when trying to print description, which makes sense because you're using %# where an integer is expected in your NSLog().
Separately, using a mutable property is almost always a bad idea. If it's really a property, you probably want to use an immutable array, and set the whole array when you want to change it.
Agree that the logging of count is wrong, but I think the other answers miss a move obvious point: the crash happens on the addObject. This implies that image is nil. NSLog that before the add.
I have used Jackson extensively on the Server side to convert from POJOs to JSON and was wondering if there is a similar library for Objective C/iPhone SDK and vice versa. Objective C does provide reflection so it should be possible to make something similar to Jackson.
You might try GoldenFleece, which converts between JSON and Objective-C objects using a convention-over-configuration pattern inspired by Jackson.
The new iOS 5 APIs provide a great facility in reading/writing JSON. These are essentially a rehash of the TouchJSON library which you can use in iOS 4. While I haven't seen much out there that will generate POCO objects from an example payload, you can create classes that are just a facade for an NSDictionary instance that the aforementioned libraries will return.
For example:
#interface PBPhoto : NSObject {
NSDictionary* data_;
}
#property (nonatomic, retain, readonly) NSDictionary *data;
- (NSString*) photoId;
- (NSString*) userId;
- (NSString*) user;
- (NSString*) title;
- (id) initWithData:(NSDictionary*)data;
#end
Implementation:
#import "PBPhoto.h"
#define PHOTO_ID #"id"
#define USER_ID #"user_id"
#define USER #"user"
#define TITLE #"title"
#implementation PBPhoto
#synthesize data = data_;
- (id) initWithData:(NSDictionary*)data {
if ((self = [super init])) {
self.data = data;
}
return self;
}
- (NSString*) photoId {
return [super.data objectForKey:PHOTO_ID];
}
- (NSString*) userId {
return [self.data objectForKey:USER_ID];
}
- (NSString*) user {
return [self.data objectForKey:USER];
}
- (NSString*) title {
return [self.data objectForKey:TITLE];
}
- (void) dealloc {
[data_ release];
[super dealloc];
}
#end
That Objective-C provides reflection may be the understatement of the year, but a lot of stuff is exposed only by the low-level C runtime and therefore is a little obtuse.
Assuming you want to take an arbitrary object and turn it into JSON, probably the smart thing is to create an NSDictionary as an intermediary, then pass it off to NSJSONSerialization (or else construct the string yourself because all of the third party libraries are quite heavyweight owing to the burden of being able to deserialise).
So, for example:
- (NSDictionary *)dictionaryOfPropertiesForObject:(id)object
{
// somewhere to store the results
NSMutableDictionary *result = [NSMutableDictionary dictionary];
// we'll grab properties for this class and every superclass
// other than NSObject
Class classOfObject = [object class];
while(![classOfObject isEqual:[NSObject class]])
{
// ask the runtime to give us a C array of the properties defined
// for this class (which doesn't include those for the superclass)
unsigned int numberOfProperties;
objc_property_t *properties =
class_copyPropertyList(classOfObject, &numberOfProperties);
// go through each property in turn...
for(
int propertyNumber = 0;
propertyNumber < numberOfProperties;
propertyNumber++)
{
// get the name and convert it to an NSString
NSString *nameOfProperty = [NSString stringWithUTF8String:
property_getName(properties[propertyNumber])];
// use key-value coding to get the property value
id propertyValue = [object valueForKey:nameOfProperty];
// add the value to the dictionary —
// we'll want to transmit NULLs, even though an NSDictionary
// can't store nils
[result
setObject:propertyValue ? propertyValue : [NSNull null]
forKey:nameOfProperty];
}
// we took a copy of the property list, so...
free(properties);
// we'll want to consider the superclass too
classOfObject = [classOfObject superclass];
}
// return the dictionary
return result;
}
Then you can use + dataWithJSONObject:options:error: on NSJSONSerialization with the returned dictionary.
To go the other way, I guess you'd use the key-value coding setValue:forKey: method, getting keys and values from a dictionary via allKeys and valueForKey:.
My code leaks but I do not know exactly what am I doing wrong. Simply I have a function that takes array with NSStrings and outputs NSString formatted as CSV.
Here is my code:
-(NSString*)generateCSVfromArray: (NSMutableArray*) reportEntries {
NSString* accumulator = [NSString stringWithString:#""];
for (NSString* string in reportEntries) {
NSString* temp = [accumulator stringByAppendingString:string];
accumulator = temp;
if (![string isEqualToString:#"\n"]) {
NSString* temp = [accumulator stringByAppendingString:#";"];
accumulator = temp;
}
}
return accumulator;
}
When I check leaks in Instruments it turns out that many string objects leaked. I managed to isolate the problem to the method above. Can you please help me and point what am I doing wrong?
I don't believe you're leaking any strings in this method. Why do you think this is the method to blame? Remember that Instruments will tell you where the object was created, but this has little to do with where it is leaked. Run the Static Analyzer for more help with that (Cmd-Shift-A).
This method is wildly inefficient, though. You're creating a ton of temporary strings. You could write this much more efficiently like this:
-(NSString*)generateCSVfromArray:(NSArray*)reportEntries {
NSMutableString* accumulator = [NSMutableString string];
for (NSString* string in reportEntries) {
[accumulator appendString:string];
if (![string isEqualToString:#"\n"]) {
[accumulator appendString:#";"];
}
}
return accumulator;
}
There are of course very good CSV writers already available. Search for "Cocoa CSV." But I assume you want this specialized algorithm.
I want to call a method which returns two values
basically lets say my method is like the below (want to return 2 values)
NSString* myfunc
{
NSString *myString = #"MYDATA";
NSString *myString2 = #"MYDATA2";
return myString;
return myString2;
}
So when i call it, i would use??
NSString* Value1 = [self myfunc:mystring];
NSString* Value2 = [self myfunc:mystring2];
I guess im doing something wrong with it, can anyone help me out?
Thanks
You can only return 1 value. That value can be a struct or an object or a simple type. If you return a struct or object it can contain multiple values.
The other way to return multiple values is with out parameters. Pass by reference or pointer in C.
Here is a code snippet showing how you could return a struct containing two NSStrings:
typedef struct {
NSString* str1;
NSString* str2;
} TwoStrings;
TwoStrings myfunc(void) {
TwoStrings result;
result.str1 = #"data";
result.str2 = #"more";
return result;
}
And call it like this:
TwoStrings twoStrs = myfunc();
NSLog(#"str1 = %#, str2 = %#", twoStrs.str1, twoStrs.str2);
You need to be careful with memory management when returning pointers even if they are wrapped inside a struct. In Objective-C the convention is that functions return autoreleased objects (unless the method name starts with create/new/alloc/copy).
You have a few options:
NSArray: Just return an array. Pretty simple.
Pointers: Pass in two pointers, and write to them instead of returning anything. Make sure to check for NULL!
Structure: Create a struct that has two fields, one for each thing you want to return, and return one of that struct.
Object: Same a structure, but create a full NSObject subclass.
NSDictionary: Similar to NSArray, but removes the need to use magic ordering of the values.
As you can only return one value/object, maybe wrap them up in an array:
-(NSArray*) arrayFromMyFunc
{
NSString *myString = #"MYDATA";
NSString *myString2 = #"MYDATA2";
return [NSArray arrayWithObjects:myString,myString2,nil];
}
You can then use it like this:
NSArray *arr = [self arrayFromMyFunc];
NSString *value1 = [arr objectAtIndex:0];
NSString *value2 = [arr objectAtIndex:1];
You could pass results back by reference, but this is easy to get wrong (syntactically, semantically, and from memory management point of view).
Edit One more thing: Make sure that you really need two return values. If they are quite independent, two separate function are often the better choice - better reusabilty and mentainable. Just in case you are making this as a matter of premature optimization. :-)
You can only directly return one value from a function. But there is a way of doing it.
-(void) myfuncWithVal1:(NSString**)val1 andVal2:(NSString**)val2
{
*val1 = #"MYDATA";
*val2 = #"MYDATA2";
}
Then to call it outside the method you'd use:
NSString* a;
NSString* b;
[self myfuncWithVal1:&a andVal2:&b];
void myfunc(NSString **string1, NSString **string2)
{
*string1 = #"MYDATA";
*string2 = #"MYDATA2";
}
...
NSString *value1, *value2;
myfunc(&value1, &value2);
Remember that you need to pass a pointer to a pointer when working with strings and other objects.
Wrap the two strings in an NSArray:
- (NSArray*)myFunc
{
NSString *myString = #"MYDATA";
NSString *myString2 = #"MYDATA2";
return [NSArray arrayWithObjects:myString, myString2, nil];
}
NSArray *theArray = [self myFunc]
NSString *value1 = [theArray objectAtIndex:0];
NSString *value2 = [theArray] objectAtIndex:1];
I see everyone has mentioned an NSArray but I'd go with an NSDictionary so the values don't have to be added in order or even at all. This means it is able to handle a situation where you only want to return the second string.
- (NSDictionary*)myFunction {
NSString *myString1 = #"string1";
NSString *myString2 = #"string2";
return [NSDictionary dictionaryWithObjectsAndKeys: myString1, #"key1", myString2, #"key2", nil];
}
NSDictionary *myDictionary = [self myFunction]
NSString *string1 = [myDictionary objectForKey:#"key1"];
NSString *string2 = [myDictionary objectForKey:#"key2"];