10

I have a 2D array of pointers to Objective-C instances to keep track of game objects on a map grid. Now I am transitioning my code to ARC, and Xcode pointed the error. I knew pointers to objects aren't allowed as struct members, but this one caught me (almost) off guard.

I understand the rationale behind the ARC constrains, but:

  1. I can't afford the overhead of objective-C arrays when looking up objects in the grid, and

  2. The objects themselves are already owned by an NSArray ivar defined in the same class that has the C-style grid as an ivar; the c-style array is only a conveniently structured shortcut. Futhermore, when objects are removed from the owning NSArray, I set the corresponding grid slot to NULL.

That is, the 2D array (grid) is just a collection of fast (but dumb) pointers to objects safely retained somewhere else (the NSArray ivar).

Is there a way to get away with this using casts? For example, define and alloc my grid as:

void*** _grid;

instead of

MyMapObjectClass*** _grid

and use (appropriately bridged) casts between void* <-> MyMapObjectClass* when setting or getting the pointers in each slot?

EDIT: So here is how I solved it

I changed the ivar declaration as described above. In addition, when setting an entry of my look-up grid, I did this:

// (Done **Only Once** at map initialization) 
// _objectArray is an instance of NSMutableArray

MyMapObjectClass* mapObject = [[MyMapObjectClass alloc] init];

// ...configure map object, etc...

// Add to Obj-C array:
[_objectArray addObject:mapObject];

// Add pointer to 2D C array:
_grid[i][j] = (__bridge void*)mapObject;

When accessing the object at (x,y), I do the opposite:

MyMapObjectClass* object = (__bridge MyMapObjectClass*) _grid[x][y];

[object performSomeMethod];

// etc...

When removing the object from the map, I do this:

MyMapObjectClass* object = (__bridge MyMapObjectClass*) _grid[x][y];

[_objectArray removeObject:object];

_grid[x][y] = NULL;

Map objects are created once at the beginning of the game, and removed according to game progress. If I need to replace a map object for another, I would do this:

MyMapObjectClass* oldObject = (__bridge MyMapObjectClass*) _grid[x][y];
// (should mark as weak?)

[_objectArray removeObject:oldObject];    
_grid[x][y] = NULL;

MyMapObjectClass* newObject = [[MyMapObjectClass alloc] init];

[_objectArray addObject:newObject];

_grid[x][y] = (__bridge void*)newObject;
Brad Larson
  • 170,088
  • 45
  • 397
  • 571
Nicolas Miari
  • 16,006
  • 8
  • 81
  • 189
  • where is the line of code that ARC is pointing out? – Omar Abdelhafith Jun 22 '12 at 11:27
  • Xcode doesn't seem to point out all errors at once, but instead in batches of 4-12. You fix some, and the next ones appear in the following check. The first error appeared in the ivar declaration of the C-array. And now, every assignment or comparison between obj-c pointer and void* is being signaled. I'm fixing it right now, and it looks like __bridge should work... – Nicolas Miari Jun 22 '12 at 11:34
  • It's curious, some of the casts are 'suggested' by Xcode's autocorrect feature, but some are just marked as errors. – Nicolas Miari Jun 22 '12 at 11:35
  • On a side note, the conversion is taking forever because I have **tons** of old-style singletons (Sound manager, texture manager, cache sprite VBO manager, GameCenter manager you name it) – Nicolas Miari Jun 22 '12 at 11:39

1 Answers1

2

Circumventing ARC using casts is generally a bad idea. The better way would be to disable ARC for your map.m (or break out just the lookup part into a separate class).Then do manual memory management inside it with retain / release and the C structures you like, as long as you do it correctly it will work fine and you will be able to call it from other classes, avoiding the overhead of nested NSArrays etc..

Community
  • 1
  • 1
Hampus Nilsson
  • 6,692
  • 1
  • 25
  • 29
  • But the pointers I have work like __unsafe_unretained. I am not circumventing ARC, just using __bridge; there should be no problem. I am keeping the NSArray and the C array in sync. The objects are retained by the NSArray (as per ARC). There is no danger of dangling pointers. – Nicolas Miari Jun 22 '12 at 12:26
  • 1
    To put it clearer: I have an ARC-friendly NSArray that owns my objects, 100% Objective-C, 100% ARC. **In addition**, I have a 'look up table' to gain quick access to my objects in a row/column fashion. The table entries are correctly updated to NULL when I remove an object from the NSArray (which gets deallocated), to avoid dereferencing a zombie. – Nicolas Miari Jun 22 '12 at 12:30
  • 2
    I would still recommend factoring it into a separate file and disable ARC for that file, as that is a more future-proof solution. If Apple ever decides to change the semantics you will not end up with compile errors for sketchy casts. Your solution will work though. – Hampus Nilsson Jun 22 '12 at 12:31
  • I understand. But this class is part of a whole group of files (Map Object, Map, Map Layer, etc.) and I'd rather have it all-or-nothing. I know ARC and non-ARC interoperate OK, but using these casts doesn't 'feel' worse than mixing compiler modes. – Nicolas Miari Jun 22 '12 at 12:43
  • Still, there seems to be no better way out, besides what I chose to do, so I'm giving you the tick and an up-vote ;) – Nicolas Miari Sep 03 '12 at 06:08