Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write my own iterator for a collection property of a property (with correct type casting)?

I have a model class with some object compositions, and I don't know the best way to write iterators for this. To see the problem in more details, here is the hierarchy (semi-pseudo code):

A Root class:

MYEntity : NSObject
@property int commonProperty;
@property NSArray *childs; //Childs of any kind.

Some concrete subclasses:

MYConcreteStuff : MYEntity
@property int number;

MYConcreteThing : MYEntity
@property NSString *string;

And a root object with concrete collections:

MYRoot : MYEntity
@property MYEntity *stuff; //Collect only stuff childs here.
@property MYEntity *things; //Collect only thing childs here.

Now I can write cool member accessors for the collections (in MYEntity), like:

-(MYEntity*)entityForIndex:(int) index
{
    if ([self.childs count] > index)
        return [self.childs objectAtIndex:index];

    return nil;      
}

And even more cool, well type casted member accessors for the root object.

-(MYConcreteThing*)thingForIndex:(int) index
{
    if ([self.things count] > index)
        return (MYConcreteThing*)[self.things entityForIndex];

    return nil;    
}

But I have no idea how to write some oneliner iterators for such collections. The wannabe client code is something like:

for (MYConcreteThing *eachThing in myRoot.things)
  eachThing.string = @"Success."; //Set "thingy" stuff thanks to the correct type.

I'm thinking of using blocks, but there could be a more clean cut solution. Any ideas/experiences?

like image 834
Geri Borbás Avatar asked Sep 23 '12 04:09

Geri Borbás


2 Answers

I'll go on with blocks for now, thats pretty straightforward. Now I prefer the term enumerate.

A block type for thing enumaration (ensuring correct type):

typedef void (^MYThingEnumeratorBlock)(MYThing *eachThing);

A cool enumerator method for Things in MYRoot (to not expose collections):

-(void)enumerateThings:(MYThingEnumeratorBlock) block
{    
    for (MYThing *eachThing in self.things.childs)
        block(eachThing);
}

So client code goes:

[myRoot enumerateThings:^(MYThing *eachThing)
 {
    NSLog(@"Thing: %@", eachThing.string);         
 }];

With some neat macro:

#define ENUMARATE_THINGS myRoot enumerateThings:^(MYThing *eachThing)

[ENUMARATE_THINGS
{
   NSLog(@"Thing: %@", eachThing.string); //Cool "thingy" properties.        
}];
like image 168
Geri Borbás Avatar answered Nov 15 '22 19:11

Geri Borbás


The best way to do this, in my opinion, would be to implement the Key Value Coding compliant methods for array properties. This would have the added bonus of making your collections observable by other objects. You can read all about it in the apple documentation. Here is an example implementation for the Things array in the MYRoot class. Feel free to personalize the code in each method:

// KVC method for read-only array of Things
- (NSUInteger) countOfThings
{
    return _things.count;
}

- (Thing*) objectInThingsAtIndex:(NSUInteger)index
{
    return [_things objectAtIndex:index];
}

// Additional KVC methods for mutable array collection
- (void) insertObject:(Thing*)thing inThingsAtIndex:(NSUInteger)index
{
    [_things insertObject:thing atIndex:index];
}

- (void) removeObjectInThingsAtIndex:(NSUInteger)index
{
    [_things removeObjectAtIndex:index];
}

To iterate over the collection you would do the following:

for (Thing *thing in [_entity valueForKey:@"things"]) {
}

To add a Thing in the array you could do

NSMutableArray *children = [_entity mutableArrayValueForKey:@"things"];
[children addObject:aThing];

In doing it this way you ensure that all object observing the @"things" property will get notified of all changes to the array. If you call the insertion methods directly they will not (this is sometimes useful in its own right).

like image 26
aLevelOfIndirection Avatar answered Nov 15 '22 19:11

aLevelOfIndirection