Re: Implementing NSFastEnumeration
Christiaan Hofman <[email protected]>
| Newsgroups | gmane.comp.macosx.devel |
|---|---|
| Message-ID | <[email protected]> |
On Oct 1, 2009, at 17:19, Gerd Knops wrote:
>
> On Oct 1, 2009, at 8:13 AM, Christiaan Hofman wrote:
>
>> I've been reading the documentation on NSFastEnumeration (both the
>> docs on the protocol and the section in the Objective-C 2.0
>> Programming Language guide). But it is really cryptic and misses
>> most of the relevant information to help in implementing it.
>
> Below some code I have been using. To access the array to be
> enumerated, it uses these methods that my code provides:
>
> - (unsigned)version;
> - (unsigned)countOfChildrenAtVersion:(unsigned)aVersion;
> - (id)childAtIndex:(unsigned)index forVersion:(unsigned)aVersion;
>
> It makes use of the extra array to keep track of the version it is
> enumerating, in case it changes while the enumeration goes on. In my
> case it is safe to use "self" as the mutationsPtr because the only
> way the child array might change is by adding additional children.
>
> Looking at it now I am not sure if the [child retain] part is
> correct, might create a memory leak. I'll have to check on that. And
> it'll need some changes to be 64-bit safe.
>
> But anyway this should get you started.
>
> Gerd
>
> - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)
> state
> objects:(id *)stackbuf
> count:(NSUInteger)len
> {
>
> // typedef struct {
> // unsigned long state;
> // id *itemsPtr;
> // unsigned long *mutationsPtr;
> // unsigned long extra[5];
> // } NSFastEnumerationState;
>
> unsigned idx=state->state;
> unsigned version=(idx>0)?state->extra[0]:[self version];
> unsigned numChildren=[self countOfChildrenAtVersion:version];
> NSUInteger now=0;
>
> while(now<len && idx<numChildren)
> {
> id child=[self childAtIndex:idx forVersion:version];
>
> stackbuf[now++]=[child retain];
> idx++;
> }
>
> state->extra[0]=version;
> state->state=(unsigned long)idx;
> state->itemsPtr=stackbuf;
> state->mutationsPtr=(unsigned long *)self;
>
> return now;
> }
>
First of all, thanks for those who replied. From the replies (and also
a test I just did) it seems the docs are simply wrong, so I filed a
radar against the docs (also asking for real docs instead of the
current useless mess).
One problem for me is that the actually returned objects are temporary
autoreleased objects. I'm sure your retain is wrong without GC. The
other thing is that I want to enumerate the underlying array of keys
first. I tried something along these lines:
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)
state
objects:(id *)stackbuf
count:(NSUInteger)len
{
NSUInteger i, count = [keys countByEnumeratingWithState:state
objects:stackbuf count:len];
for (i = 0; i < count; i++)
{
id key = state->itemsPtr[i];
id value = [values objectForKey:key];
stackbuf[i] = value;
}
state->itemsPtr = stackbuf;
return count;
}
While this works for small arrays, for larger arrays this gives a
memory access crasher. The reason seems to be that NSArray can return
more items than len (the size of stackbuf) in a single call.
Would there be a way to modify this implementation to work? Or am I
forced to get the objects from keys without using fast enumeration (in
which case fast enumeration becomes hardly fast)?
And another question I have been unable to find an answer to in the
docs. Foundation collections are implemented as class clusters. So is
NSFastEnumeration implemented as a primitive or a derived method? In
other words: when I subclass NSArray (or another collection class),
does it already have a default implementation of NSFastEnumeration, or
am I forced to always implement it when I want to use for(...in...)?
thanks,
Christiaan