简体   繁体   中英

In Objective-C, what is a proper way to override synthesized setter?

I was originally going to write:

-(void) setLeftChild:(NSNode *) leftChildNode {

     _leftChildNode = leftChildNode;
     leftChildNode.parent = self;
     // ...
}

to set all the appropriate values for a node's child, and the child node's parent node should point back to self, and the child node's tree object should be the same as parent's tree object (the tree where the nodes belong to).

But then I realized that doing so will affect the simple line node.leftChild = something; So this line will have "side effects" that may otherwise be unexpected.

Also, what if setLeftChild actually use other property's setter (such as using leftChildNode.parent = self , which is the proper interface), and those setters in turn use the node's leftChild setter -- it can become an infinite loop this way. It will be not so good if some special case causes this infinite loop, and you always have to worry about potential infinite loop.

Another way is never override any synthesized setter, but just use the name addLeftChildNode instead of setLeftChildNode , and inside addLeftChildNode , we can use the property setter freely with no worries. But what if we really want to "set" it -- the name "add" it can mean add it only if it is empty, so the name "set" is more appropriate. If I call it addLeftChild instead of addLeftChildNode , then these two names are confusing. Maybe if a convention is used, such as calling it setAndConfigLeftChildNode , then it won't be confusing and obviously not overriding the setter, and can be a convention to follow in the code. (to use setAndConfig instead of overriding the setter)

So we have the issue of:

1) unexpected side effect of a overridden setter (at the beginning of question)
2) infinite loop
3) naming of methods, can be confusing

Is there a good common / best practice?

Good question... it really does boil down to preference but I wish to point out that you can also change the setter function's name with the following:

@property (setter=assignLeftChildNode) NSNode *leftChildNode;

So you have a little more flexibility with regards to naming conventions... setLeftChildNode doesn't have to be taken by the synthesized setter. I know this isn't a direct answer to your question but I hope this helps.

I think it's fine to have some side-effects in a custom setter, in certain circumstances. For instance, I often want an object to use KVO to monitor the properties of another object that I need to switch out often. In your situation, the integrity of the data structure will be compromised unless these actions are carried out exactly when the property is set. So it makes sense to me to do them in your setter. I think it's just common sense. Anything that really has to happen whenever this property changes should happen in the setter, otherwise you'll forget some time .

Infinite recursion is a real danger so overriding the setter should absolutely be done with care. You'll want to put some defensive coding in practice here.

Avoiding confusing method names is crucial, especially in a platform as naming-convention-centric as this. You are right to take this into consideration.

Overall, I think you are on the right track here. This is a good example of when to have side effects in your setter, and I think your naming choice is appropriate. Out of an abundance of caution toward infinite recursion you might want to work with the instance directly (ie _leftChild instead of self.leftChild ) inside this method.

Apart from the naming conventions (which have already been discussed in your other question), I would not override the setter function because (as you already noticed) the side effect are not easily to oversee. I would use a separated method, eg setLeftTree: .

And if your node already has a left child and you set a new one, you should also consider to set the parent of the previous child to nil :

- (void)setLeftTree:(NSNode *)leftChildNode
{
    if (self.leftChild != nil)
        self.leftChild.parent = nil;
    self.leftChild = leftChildNode;
    leftChildNode.parent = self;
}

Having thought about this a bit I think the answer is that the relationship needs to be controlled totally from one side. Given that the parent property of your child node is probably weak to stop retain cycles, we will designate the parent as being the owner of the relationship. The simplest expression of this is that the method that sets the parent is private to the class ( NB do not call the class NSNode , the NS prefix is reserved by Apple ). It should not be called setParent because we do not want -setValue:forKey: to work on it or dot notation. This method just sets the parent ivar. All of the logic to do with maintaining referential integrity should be in the setLeftChild: (and presumable setRightChild: ) method. Your implementation will look like this:

// Assuming you are using ARC
@interface MyNode()

-(void) privateSetParent: (MyNode*) newParent; 

@end

@implementation MyNode
{
    _weak MyNode* _parent;
    MyNode* _leftTree;
    MyNode* _rightTree;
}

-(void) privateSetParent: (MyNode*) newParent
{
    _parent = newParent;
}

-(void) setLeftTree: (MyNode*) newTree
{
    [[newTree parent] remove: newTree];
    [_leftTree privateSetParent: nil];
    _leftTree = newTree;
    [newTree privateSetParent: self];
}

-(void) remove: (MyNode*) subTree
{
    if (_leftTree == subTree)
    {
        _leftTree = nil;
        [subTree privateSetParent: nil];
    }
    if (_rightTree == subTree)
    {
        _rightTree = nil;
        [subTree privateSetParent: nil];
    }   
} 

@end

You can still have a read only parent property and you can still do KVO with manual change notification .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM