初始化属性,点符号表示法

22

在我的init方法中,使用点符号将retain属性初始化为nil是不好的想法吗?

对于任何普通的属性,例如:

@property (nonatomic, retain) id foo;

假设在我的init方法中我设置了self.foo = nil。合成的方法首先会释放或自动释放foo(不确定其底层实现)。在第一次调用setter或getter之前,foo是否保证是nil?或者除非我显式地使用点符号设置foo = nil,否则它会指向随机的垃圾值?

1个回答

77

在我的init方法中使用点符号将retain属性初始化为nil是一个不好的想法吗?

是的,这是一个糟糕的想法。

1) 在alloc+init序列中,对象已经被清零,因此将其分配为nil是没有必要的。换句话说,在访问器中除非有副作用(在这个阶段应该避免访问器中的副作用),否则这个调用是无用的。

2) 在部分构建状态(例如initdealloc)中,您不应该使用被覆盖的方法来向self发送消息。

是否有第二个原因?我经常在我的init方法中使用self.array = [NSMutableArray array];这样的方法。

原因是您的对象在部分构建状态(init..., dealloc, finalize以及许多copyWithZone:实现)时不应该关心类接口的行为。您的类应该对其成员进行适当的初始化(如init...)和清理(如dealloc),而不引入副作用。

考虑以下示例,您可以将其构建为OS X的基础工具:

#import <Foundation/Foundation.h>

enum { UseItTheRightWay = true -OR- false };

@interface MONObjectA : NSObject
{
    NSMutableArray * array;
}

@property (nonatomic, retain) NSArray * array;

@end

@implementation MONObjectA

@synthesize array;

- (id)init
{
    self = [super init];
    if (0 != self) {
        NSLog(@"%s, %@",__PRETTY_FUNCTION__, self);
        if (UseItTheRightWay) {
            array = [NSMutableArray new];
        }
        else {
            self.array = [NSMutableArray array];
        }
    }
    return self;
}

- (void)dealloc
{
    NSLog(@"%s, %@",__PRETTY_FUNCTION__, self);
    if (UseItTheRightWay) {
        [array release], array = nil;
    }
    else {
        self.array = nil;
    }
    [super dealloc];
}

@end

@interface MONObjectB : MONObjectA
{
    NSMutableSet * set;
}

@end

@implementation MONObjectB

- (id)init
{
    self = [super init];
    if (0 != self) {
        NSLog(@"%s, %@",__PRETTY_FUNCTION__, self);
        set = [NSMutableSet new];
    }
    return self;
}

- (void)dealloc
{
    NSLog(@"%s, %@",__PRETTY_FUNCTION__, self);
    [set release], set = nil;
    [super dealloc];
}

- (void)setArray:(NSArray *)arg
{
    NSLog(@"%s, %@",__PRETTY_FUNCTION__, self);
    NSMutableSet * tmp = arg ? [[NSMutableSet alloc] initWithArray:arg] : nil;
    [super setArray:arg];
    [set release];
    set = tmp;
}

@end

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    [[MONObjectB new] release];

    /* the tool must be named 'Props' for this to work as expected, or you can just change 'Props' to the executable's name */
    system("leaks Props");

    [pool drain];
    return 0;
}

在这个测试中,切换行为的主要开关是UseItTheRightWay

如果UseItTheRightWaytrue,我们会得到结果:

2011-05-09 01:52:11.175 Props[45138:a0f] -[MONObjectA init], <MONObjectB: 0x10010c750>
2011-05-09 01:52:11.177 Props[45138:a0f] -[MONObjectB init], <MONObjectB: 0x10010c750>
2011-05-09 01:52:11.179 Props[45138:a0f] -[MONObjectB dealloc], <MONObjectB: 0x10010c750>
2011-05-09 01:52:11.179 Props[45138:a0f] -[MONObjectA dealloc], <MONObjectB: 0x10010c750>
leaks Report Version:  2.0
Process:         Props [45138]
< --- snip --- >        
Process 45138: 1581 nodes malloced for 296 KB
Process 45138: 0 leaks for 0 total leaked bytes.

如果UseItTheRightWayfalse,则我们会得到结果:

2011-05-09 01:55:51.611 Props[45206:a0f] -[MONObjectA init], <MONObjectB: 0x10010c750>
2011-05-09 01:55:51.614 Props[45206:a0f] -[MONObjectB setArray:], <MONObjectB: 0x10010c750>
2011-05-09 01:55:51.615 Props[45206:a0f] -[MONObjectB init], <MONObjectB: 0x10010c750>
2011-05-09 01:55:51.617 Props[45206:a0f] -[MONObjectB dealloc], <MONObjectB: 0x10010c750>
2011-05-09 01:55:51.618 Props[45206:a0f] -[MONObjectA dealloc], <MONObjectB: 0x10010c750>
2011-05-09 01:55:51.618 Props[45206:a0f] -[MONObjectB setArray:], <MONObjectB: 0x10010c750>
leaks Report Version:  2.0
Process:         Props [45206]
 < --- snip --- >    
Process 45206: 1585 nodes malloced for 297 KB
Process 45206: 1 leak for 48 total leaked bytes.
Leak: 0x100110970  size=48  zone: DefaultMallocZone_0x100005000 instance of 'NSCFSet', type ObjC, implemented in Foundation 
    0x70294ff8 0x00007fff 0x00001080 0x00000001     .O)p............
    0x00000001 0x00000000 0x00000000 0x00010000     ................
    0x707612a8 0x00007fff 0x00000000 0x00000000     ..vp............

问题#1

这个例子明显的问题在于dealloc中引入的泄漏。

问题#2

第二个容易让你遇到麻烦的问题更微妙:

-[MONObjectA init]
-[MONObjectB setArray:]
-[MONObjectB init]

这是什么鬼?-[MONObjectB setArray:]被调用在 -[MONObjectB init] 甚至 -[MONObjectA init]退出之前?这意味着在 -[MONObjectB init] 之前就使用了 MONObjectB的实现,甚至在 -[MONObjectA init]退出之前。这不好 =\

复杂的设计将带来一堆不良副作用、奇怪的行为、泄漏等问题。复杂的设计会在非常明显和微妙的方式上失败,这些问题很难跟踪。最好从一开始就以正确的方式编写类,避免因这种微小的差异而引起的维护问题(即使您可以在相当长的时间内这样做而没有明显的副作用)。


谢谢。 #2 有什么特别的原因吗?我经常在我的 init 方法中使用 self.array = [NSMutableArray array]; - Morrowless
3
很棒的演示!直到现在我才明白其含义。 - Morrowless
我对此有点陌生,抱歉表达不清。我只是想知道您是如何创建UseItTheRightWay为true和false的打印输出(即“我们得到了结果”部分)。 - Sean Danzeiser
@SeanDanzeiser 哦,好的。在这个例子中做我所做的事情非常不寻常。我这样做是为了它是独立自包含的。你不应该在生产代码中使用这种方法--它可以用于小型概念验证。话虽如此,这种方法在iOS上不起作用。但是,通常有更有用的工具可供使用--特别是Instruments->泄漏。现在回答你的问题:找到说system("leaks Props");的部分,就在main返回之前--这就像在Terminal.app中输入leaks SOME_APP_NAME一样。为了保持演示简洁,输出被手动剪裁了。 - justin
@Justin,请问你能帮我理解内存泄漏是如何引入的吗?我没有理解到。 - andrey.krukovskiy
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接