在Objective-C中,什么是覆盖合成setter的正确方法?

时间:2012-09-08 03:53:30

标签: objective-c properties

我原本打算写:

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

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

为节点的子节点设置所有适当的值,子节点的父节点应该指向self,子节点的树对象应该与父节点的树对象(节点所属的树)相同。

但后来我意识到这样做会影响简单的行node.leftChild = something;所以这一行会有“副作用”,否则可能会出乎意料。

此外,如果setLeftChild实际使用其他属性的setter(例如使用leftChildNode.parent = self,这是正确的接口),并且那些setter依次使用节点的leftChild setter,该怎么办?它可以通过这种方式变成无限循环。如果某些特殊情况导致这种无限循环,那就不太好了,你总是要担心潜在的无限循环。

另一种方法是永远不会覆盖任何合成的setter,但只使用名称addLeftChildNode而不是setLeftChildNode,而在addLeftChildNode内,我们可以自由地使用属性setter而不用担心。但是,如果我们真的想要“设置”它 - 名称“添加”它可能意味着只有在它为空时添加它,所以名称“set”更合适。如果我将其称为addLeftChild而不是addLeftChildNode,那么这两个名称就会令人困惑。也许如果使用约定,例如调用它setAndConfigLeftChildNode,那么它不会混淆,显然不会覆盖setter,并且可以成为代码中遵循的约定。 (使用setAndConfig而不是覆盖setter)

所以我们有以下问题:

1)被覆盖的二传手的意外副作用(问题开头)
2)无限循环
3)命名方法,可能会令人困惑

有一个很好的共同/最佳实践吗?

4 个答案:

答案 0 :(得分:3)

好问题......它确实归结为偏好但我想指出你也可以用以下内容更改setter函数的名称:

@property (setter=assignLeftChildNode) NSNode *leftChildNode;

因此,您在命名约定方面有一点灵活性...... setLeftChildNode不必由合成的setter使用。{{1}}。我知道这不是你问题的直接答案,但我希望这有帮助。

答案 1 :(得分:2)

我认为在某些情况下,在自定义设置器中有一些副作用是很好的。例如,我经常希望一个对象使用KVO来监视我需要经常切换的另一个对象的属性。在您的情况下,除非在设置属性时执行这些操作,否则数据结构的完整性将受到影响。因此,在你的二传手中做这些是有道理的。我认为这只是常识。每当此属性更改时,任何真正 的事情都会发生在setter中,否则您将忘记一些时间

无限递归是一个真正的危险,因此绝对应该谨慎地完成设定者。你想在这里练习一些防御性编码。

避免混淆方法名称至关重要,尤其是在以此命名为约定的平台中。你考虑到这一点是正确的。

总的来说,我认为你在这里走在正确的轨道上。这是何时在你的setter中产生副作用的一个很好的例子,我认为你的命名选择是合适的。出于对无限递归的充分谨慎,您可能希望在此方法中直接使用实例(即_leftChild而不是self.leftChild)。

答案 2 :(得分:1)

除了命名约定(已在您的其他问题中讨论过)之外,我不会覆盖setter函数,因为(正如您已经注意到的)副作用不容易监督。我会使用一种分离的方法,例如setLeftTree:

如果您的节点已经有一个左子节点并且您设置了一个新子节点,您还应该考虑将前一个子节点的父节点设置为nil

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

答案 3 :(得分:1)

考虑到这一点,我认为答案是需要从一方完全控制关系。鉴于您的子节点的parent属性可能weak可能会停止保留周期,我们会将父节点指定为关系的所有者。最简单的表达方式是设置父级的方法对于类是私有的( NB不调用类NSNode,NS前缀由Apple保留)。它不应该被称为setParent因为我们不希望-setValue:forKey:使用它或点符号。此方法仅设置父ivar。维护引用完整性的所有逻辑都应该在setLeftChild:(和可能的setRightChild:)方法中。您的实现将如下所示:

// 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

您仍然可以拥有只读parent属性,但您仍然可以使用manual change notification执行KVO。