SteveCaine
SteveCaine

Reputation: 512

Objective-C custom property getter infinite recursion

I have an Objective-C class that implements a node for a data tree. Its properties are read-only to the public while a private extension of the class (not shown here) implements the properties' setters so a manager class can create the tree.

// Interface
@interface DataSet : NSObject {
    NSString        *name;
    NSString        *data;
@private
    DataSet         *parent;
    NSMutableArray  *children;
}
@property (nonatomic, readonly, copy) NSString *name;
@property (nonatomic, readonly, copy) NSString *data;

I want to implement a custom getter for one of the properties that, if the property is nil, will walk up the tree until it finds an ancestor node that has a non-nil value for that property.

My problem is implementing the getter without causing an infinite recursion of the getter calling itself.

// Implementation
@interface DataSet ()
@property (nonatomic, retain) DataSet           *parent;
@property (nonatomic, retain) NSMutableArray    *children;
@end

@implementation DataSet

@synthesize name;
// do not @synthesize data
@synthesize parent, children;

// custom getter walks up tree to find first non-nil 'data' property
- (NSString*) data {
    NSString *result = nil;
    DataSet *set = self;
    while (set != nil && result == nil) {
        result = [set data];    // <=== INFINITE RECURSION HERE
        set = set.parent;
    }
    return result;
}

I've searched through this and other forums but haven't found any examples of what I'm trying to do here. Anyone have any suggestions?

Also, should the last line in the getter be

return [result copy];

?

Upvotes: 2

Views: 1980

Answers (3)

SteveCaine
SteveCaine

Reputation: 512

Solved it.

I can call the custom getter recursively on an object's parent (but not on the object itself). That's not the problem.

What's key is NOT to use the custom getter in dealloc when I'm releasing the object, or else some 'data' objects will be returned (and thus released) multiple times.

// custom getter - if data is nil on this object, 
// find the first non-nil value in its list of ancestors
- (NSString*) data {
    NSString *result = data;
    if (result == nil && parent != nil)
        result = [parent data];
    return result;
}

- (void) dealloc {
    NSInteger count = [children count];
    for (NSInteger index = count - 1; index >= 0; --index) {
        DataSet *child = [children objectAtIndex:index];
        [children removeLastObject];
        [child release];
    }
    [children release];

    // DON'T call custom getter if data is nil
    // or we'll get one of its ancestors' data object, release it,
    // then later release it again when releasing the ancestor
    if (data != nil)
        [self.data release];

    [self.name release];

    [super dealloc];
}

Upvotes: 0

drekka
drekka

Reputation: 21883

Hmmm, I think you want something like this:

-(NSString *) data {
    // Determine result from current instance data.
    NSString *result = ....; 

    // If nothing, ask parent instance of this instance.
    if (result == nil) {
        result = [parent data];
    }

    // Might still be nil if parent returns nothing.
    return result;
}

Hmmm, actually seeing as you have a data variable containing some textural data, it could be done like this:

-(NSString *) data {
    // If data is nil, ask parent instance for a value, otherwise return a copy.
    return data == nil ? [parent data] : [data copy];
}

So each instance of DataSet doesn't need to have a loop. All they do is check with their immediate parent. This way if you have a data graph of A -> B -> C -> D and execute [D data]; the D will check itself and then as C, which will check itself and then as B, which will check itself and then ask A. You will get back the first successful non-nil value for result.

Upvotes: 5

Adam Rosenfield
Adam Rosenfield

Reputation: 400274

Just access the ivar directly:

// custom getter walks up tree to find first non-nil 'data' property
- (NSString*) data {
    NSString *result = nil;
    DataSet *set = self;
    while (set != nil && result == nil) {
        result = set->data;
        set = set->parent;
    }
    return [result copy];
}

This avoids calling the property accessor and therefore avoids recursion.

And yes, the last line should be return [result copy];, because you declared your property as having the copy property. If you hadn't declared it with the copy property, then you would not return a copy.

Upvotes: 3

Related Questions