2

I ran into an error when I was trying to store a C struct into a NSArray (I was able to solve it by converting into NSData as specified in the link below). However, I am curious to understand why it did not work without all this hassle of using NSData. link to solution I used

Please note: I don't want a solution, it is already solved. I am just trying to improve my understanding of the fundamentals of Objective-C by understanding why the direct approach below did not work.

What I was trying to do: A simple program that allows a person to add a grocery item and mark it as done after adding to shopping cart

  1. Define C struct with 3 elements (name of item, count and BOOL to keep track of whether it has been added to cart or not)

  2. Each time a new item is created I do the following groceryItem *secondGroceryItem = malloc(sizeof(groceryItem)) ; secondGroceryItem->itemName="Beer" ; secondGroceryItem->count = 3 ; secondGroceryItem->purchased=NO ;

  3. Then I add it to a NSMutableArray keeping track of grocery items using [myGroceryList addObject:(__bridge id)(secondGroceryItem)]

where myGroceryList is defined earlier as NSMutableArray *myGroceryList = [NSMutableArray array] ;

This does not work. I had to convert into a NSData before storing in NSMutableArray. Why can't I just store a pointer to my C struct directly in the NSMutableArray?

Community
  • 1
  • 1
Smart Home
  • 801
  • 7
  • 26
  • 1
    A C struct is not an NSObject. Only NSObject *pointers* can be stored in NSArrays. You can (under some circumstances) use an NSValue as the "container" for your C struct. Probably better than some NSData kluge, but still kinda flaky. (And there are hardly ever cases where you'd need to use C structs unless you're doing some really weird stuff.) – Hot Licks Nov 28 '14 at 00:01
  • 1
    I'm not sure this question is really answerable. The simple answer is, as Hot Licks said "because `NSArray` can only store objects". The more complicated answer means going back to the decisions that NeXT engineers made 20+ years ago. At any rate, the recommended solution is to convert your structure into a simple data class, making the fields into properties. – jscs Nov 28 '14 at 00:06
  • We also can't put ints, floats, chars, etc., in an `NSArray`. An `NSArray` is a special type of array of Objective-C objects. – nhgrif Nov 28 '14 at 00:12

2 Answers2

10

It should be noted that the reason we can't put C-structs into NSArray isn't because of anything special about structs. Instead, we should consider NSArray to be a special type of array.

We can put structs in arrays in Objective-C. We know this because C programmers put structs in arrays.

NSArray (and NSMutableArray and all the other NS collection objects) is a special class designed to implement the array data structure specifically for Objective-C objects. The fact that NSArray only allows Objective-C objects allows NSArray to do things like makeObjectsPerformSelector:. We're sending an Objective-C message to everything in the array. We couldn't allow this if we allowed non-Objective-C-objects into the array.

It's not just structs that aren't allowed into NSArray. All the primitive data types are not allowed in an NSArray. This even includes NSInteger and CGFloat, because remember, these are typedef'd primitives--not objects.

It should be noted however, that NSArray is not the only array-like data structure available to us in Objective-C. Remember, Objective-C is a strict superset of C. And as I already mentioned, C programmers are already putting structs into an array.

We can always use a C-style array. In fact, although it's very rarely done (because of the power of Objective-C arrays), we can even create a C-style array of Objective-C objects.

Consider:

NSString *stringArray[3];
stringArray[0] = @"foo";
stringArray[1] = @"bar";
stringArray[2] = @"baz";

for (int i = 0; i < 3; ++i) {
    NSLog(@"%@", stringArray[i]);
}

This compiles, runs, and does exactly what you'd expect in Objective-C. As such, we can do exact same with a struct:

typedef struct Foo {
    int x;
    int y;
} Foo;

Foo fooArray[3];
fooArray[0] = (Foo){.x = 1, .y = 2};
fooArray[1] = (Foo){.x = 3, .y = 4};
fooArray[2] = (Foo){.x = 5, .y = 6};

for (int i = 0; i < 3; ++i) {
    NSLog(@"Foo(x: %i, y: %i)", fooArray[i].x, fooArray[i].y);
}

Again, this is perfectly valid Objective-C code.

The question you have to ask yourself: Is it better to have a struct and deal with a C-style array, or would my struct be better as an object that I can put in an NSArray? I wouldn't use a struct that I'm converting to NSData and back just for the sake of putting it into an NSArray...that seems like a mess.

devios1
  • 36,899
  • 45
  • 162
  • 260
nhgrif
  • 61,578
  • 25
  • 134
  • 173
3

An NSArray or NSMutableArray consists of objects. C datatypes are not objects. So you can not store them directly in a NSArray.

I assume that you know how to create an object. Include in this object the variables with the C-datatype.

Once you have that, add that object to the NSArray:

NSMutableArray *yourArray = [[NSMutableArray alloc] init];

YourObject *object1 = [[YourObject alloc] init];
[yourArray addObject: object1];
Vincent
  • 4,342
  • 1
  • 38
  • 37
  • Thanks, it just seems an overkill to create a class for every small thing. I guess I'll have to compare this against the overhead of having to go through NSData kludge or decide if I will need any of the special things NSArray gives me (i.e use a regular array instead) on a case-by-case basis. – Smart Home Nov 29 '14 at 13:11
  • This does also apply to NSDATA. – Vincent Nov 29 '14 at 18:21