coding with objc & swift

在Init和Dealloc中使用accessor

| Comments

过去几年Cocoa社区已经发生了很大变化。曾经,在init/dealloc中使用accessor方法是很令人生厌。这种做法彻底地被认为是错误的。他们的说法是:最好直接操作实例变量,而不使用accessor方法。并且建议像下面这样写代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (id)initWithWhatever:(id)whatever
{
    if((self = [self init]))
    {
        _whatever = [whatever retain];
    }
    return self;
}

- (void)dealloc
{
    [_whatever release];

    [super dealloc];
}

对应的使用accessor的版本,会这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (id)initWithWhatever:(id)whatever
{
    if((self = [self init]))
    {
        [self setWhatever:whatever];
    }
    return self;
}

- (void)dealloc
{
    [self setWhatever: nil];

    [super dealloc];
}

使用accessor的优点

和其他地方一样,在init/dealloc中使用accessor有这样一些的优点和:它们使你的代码和实现解耦,并且还可以减轻你在内存管理方面的工作。

回忆一下,有过多少次你意外的把代码写成这样:

1
2
3
4
5
- (id)init
{
    _ivar = [NSArray arrayWithObjects:...];
    return self;
}

我想答案应该是多种多样的吧(我已经很久没有犯过这种错误了)?但是使用accessor就可以确保你不会犯这种错误:

1
2
3
4
5
- (id)init
{
    [self setIvar: [NSArray arrayWithObjects:...]];
    return self;
}

此外,你也许有一些附属状态(像缓存,统计等等这些)需要在对象的值变化的时候进行设置或者关闭。正确的使用accessor可以保证不需要过多重复的代码,所有的这些都能够在对象被创建时或者被销毁时正常的工作。(译注类似KVO这种情形)

使用accessor的缺点

使用accessor的缺点可以简单的总结成一句话:有副作用。有时候这些副作用会在init/dealloc中产生不良影响。

在写一个setter方法的时候,如果你打算在init/dealloc里面调用它,要确保它的行为正确。这意味着要处理对象没有完全被创建时的情况。

更糟糕的情况是,如果你一旦要在子类中重写setter方法,你需要将它写成能够处理父类用它来初始化或销毁实例变量的情况。比如,这段看上去很正常的代码却有潜在的危险:

1
2
3
4
5
6
7
8
9
10
11
- (void)setSomeObj:(id)obj
{
    [anotherObj notifySomething];
    [super setSomeObj:obj];
}

- (void)dealloc
{
    [anotherObj release];
    [super dealloc];
}

如果父类用setter方法来销毁someObj,就会在dealloc中[anotherObj release]已经执行后再执行子类重写的那个版本的setter方法。重载的setter方法就会去操作一个野指针,很可能就会来一个漂亮的崩溃。

要优雅地修正这个问题不是很难。在dealloc中释放后将anotherObj指向nil就行了。一般情况下,要使你的重载行为保持正常并不是很困难,但是如果你要这样用accessor,那么一定要记住这一点。

键-值观察者(KVO)

这类讨论中经常提到的一个话题就是键-值观察者,因为accessor的副作用通常都是KVO引起的。和其他情况类似,当对象没有被完全创建的时候或者已经被销毁后,KVO的调用就会带来不幸的事情。但是我个人认为这是个不太靠谱的说法。

因为99%的情况下都是,只有一个对象被完全创建后KVO才能被设置到对象上,并且会在对象被销毁前撤销掉。实际情况下,KVO不太可能会在对象创建前就被激活或者对象销毁后才被终止。

在你的对象完全初始化前,外部代码不可能对你的对象做任何操作。除非你的初始化方法自己要把指针到处传递给外部对象。同样,当你的对象正在执行dealloc方法的时候,外部对象也不可能继续保持对你的对象的KVO引用。父类的代码确实会在这个时候执行,但是父类的代码不太可能会导致外部对象可以观察一个它自己甚至都没有的属性。 > [译注:这段后半部分的意思没太懂,保留原文给各位]

It’s not possible for outside code to do anything with your object until it’s fully initialized, unless your initializer itself is passing pointers around to outside objects. And likewise, it’s not possible for outside code to keep a KVO reference to your object as it’s executing its dealloc method, because it’s too late for it to remove it, unless your dealloc triggers something that allows it to do so. Superclass code does execute in these timeframes, but superclass code is extremely unlikely to do anything to cause external objects to observe properties that the superclass itself doesn’t even have.

结论

现在优点和缺点你都知道了,你会在init和dealloc中使用accessor吗?依我看,用或不用都可以。用,也没太大的优势;不用,也没什么缺点。我的绝大多数的实例变量都没用accessor方法,但是都是在我对变量做了一些特殊操作的时候,这些情况下不使用accessor会带来更多的好处,我才没使用accessor方法。除此之外,我会毫不犹豫地使用accessor来帮我解决一些麻烦事。

原文:Friday Q&A 2009-11-27: Using Accessors in Init and Dealloc

Comments