Reputation: 512
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
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
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
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