With NSPointerArray, how to iterate over opaque pointers?

3.5k views Asked by At

I recently discovering these classes like NSMapTable and NSPointerArray, which work like the traditional collections, but also let you store weak references or plain old C pointers. Unfortunately it looks like you can't use the for...in syntax to iterate over non-NSObject pointers. For example:

typedef struct Segment {
    CGPoint bottom, top;
} Segment;
...
NSPointerArray *segments = [[NSPointerArray alloc] 
                                 initWithOptions:NSPointerFunctionsOpaqueMemory];
...
Segment *s = malloc(sizeof(Segment));
[segments addPointer: s];
...
for (Segment *s in segments) {   // nope...

The compiler does not like that last line. The error:

Selector element type 'Segment *' (aka 'struct Segment *') is not a valid object

So, do I need to do this?

for (int i=0, len=segments.count; i<len; i++) {
    Segment *seg = [segments pointerAtIndex:i];
    ...

That's not the end of the world, but I just want to make sure.

2

There are 2 answers

1
Lance On

the for (... in ...) syntax won't work in this case because Segment is a struct, not an Objective C object. Your second for loop should work.

0
Martin R On

(This might be more of theoretical interest.) NSPointerArray does conform to the NSFastEnumeration protocol, it is only the for (id object in collection) language construct that cannot be used with arbitrary pointers which are not Objective-C pointers.

But you can get a whole bunch of pointers from the array by calling the NSFastEnumeration method countByEnumeratingWithState:objects:count: directly. This is a bit tricky because that method need not fill the supplied buffer (as explained here: How for in loop works internally - Objective C - Foundation).

Here is a simple example how this would work:

__unsafe_unretained id objs[10];
NSUInteger count = [segments countByEnumeratingWithState:&state
                                                 objects:objs count:10];

// Now state.itemsPtr points to an array of pointers:
for (NSUInteger i = 0; i < count; i++) {
    Segment *s = (__bridge Segment *)state.itemsPtr[i];
    NSLog(@"%p", s);
}

So this does not help to make the code simpler and you probably want to stick with your explicit loop.

But for large arrays it might improve the performance because the pointers are "fetched" in batches from the array instead of each pointer separately.