为啥在 Objective-C 中执行 alloc 和 init 在单独的语句中会导致对象根据 Xcode 静态分析器被释放?

Posted

技术标签:

【中文标题】为啥在 Objective-C 中执行 alloc 和 init 在单独的语句中会导致对象根据 Xcode 静态分析器被释放?【英文标题】:Why in Objective-C does doing alloc and init in separate statements cause the object to be released according to the Xcode static analyzer?为什么在 Objective-C 中执行 alloc 和 init 在单独的语句中会导致对象根据 Xcode 静态分析器被释放? 【发布时间】:2011-09-21 10:41:40 【问题描述】:

我正在使用 Objective-C 迈出第一步,但在 XCode 4.1 中的静态分析器 (Product->Analyze) 中遇到了一个小问题,尽管令人困惑。我为有理数创建了一个简单的 Fraction 类,我像这样分配和初始化它,

Fraction* f = [[[ Fraction alloc ] initWithNumerator:3 withDenomimator:5]
                                                               autorelease];
[ f print ];

其中print 是一种仅使用NSLog 显示分数的方法,一切正常。但是,如果我将 alloc/init 构造拆分为两个语句(我意识到这是非惯用的 - 我只是想了解机器)并使用手动 release 而不是 autorelease 给出:

Fraction* f = [ Fraction alloc ]; // 1. Method returns an Objective-C object with
                                  //    a +1 retain count (owning reference)
[ f initWithNumerator:3 withDenomimator:5]; // 2. Object released
[ f print ]; // 3. Reference-counted object is used after it is released
[ f release ];

程序似乎仍然可以正常运行,但 XCode 分析器在 cmets 中给出警告。为什么 XCode 认为 init 调用会导致对象被释放?

在我提出问题时进一步考虑这一点,我可以看到我的两个程序并不完全相同,因为在第一个 sn-p 中,我的指针 f 是调用 init 的结果,而在第二个 sn -p 它是alloc 的结果。所以将我的代码更改为,

 Fraction* a = [ Fraction alloc ];
 Fraction* f = [ a initWithNumerator:3 withDenomimator:5];
[ f print ];
[ f release ]; // or should it be [ a release ] ?

使其完全等效,静态分析器停止抱怨。那么init 是否有可能返回与从alloc 传递给它的指针不同的指针,而不仅仅是配置它已传递的内存?使用此代码,我应该将[ a release ]alloc[ f release ]init 配对吗?

【问题讨论】:

【参考方案1】:

那么,init 是否有可能返回一个与从 alloc 传递给它的指针不同的指针,而不仅仅是配置它已传递的内存?

绝对。

使用此代码,我应该将 [ a release ] 与 alloc 或 [ f release ] 与 init 配对吗?

您将初始化对象的值分配给f(就像您一样)。此时,a 可能是一个悬空指针(如果返回另一个地址)。因此,f 应该是 released。

对这个顺序的解释是,对象可能选择返回自己的特殊版本/变体,并且这种重新分配发生在 init... 链上。

愚蠢的示范:

@interface Fraction : NSObject

@private
    int numerator;
    int denominator;


@end

static Fraction* EvilFraction = ...;

@implementation Fraction

- (id)initWithNumerator:(int)num denominator:(int)den

    self = [super init];
    if (nil != self) 
        if (0 == den)
            [self release];
            return [EvilFraction retain];
        
    
    return self;


@end

【讨论】:

【参考方案2】:

根据+[NSObject alloc] 的文档,Apple 明确建议不要尝试分离对 +alloc-init 的调用:

必须使用 init... 方法来完成初始化过程。 例如:

TheClass *newObject = [[TheClass alloc] init];

-[NSObject init]:

在某些情况下,init 方法可能会释放新对象并返回 一个替代品。因此程序应该总是使用返回的对象 通过 init,不一定是 alloc 返回的那个 allocWithZone:,在后续代码中。

静态分析器在抱怨,因为它预计 +alloc 将与 -init 配对。因为-init 可以释放发送者,因此静态分析器认为您正在尝试调用已释放实例上的方法。

【讨论】:

【参考方案3】:

那么init 是否有可能返回与从alloc 传递给它的指针不同的指针,而不仅仅是配置它已传递的内存?

是的,就是这样。这也是您不只是在初始化程序中调用[super init] 的原因;相反,您调用它并将结果分配给self,因为您不一定要返回相同的实例。

相关文档在The Objective-C Programming Language。

使用此代码,我应该将[ a release ]alloc[ f release ]init 配对吗?

如果你真的想做这样的事情,你必须先检查它们是否不相等,否则你会过度释放。但真正的解决方案是不要拆分allocinit。这是在 Objective C 中实例化对象的惯用方式,而以一种不同的方式来实现,你必须添加额外的代码,这会适得其反。

【讨论】:

以上是关于为啥在 Objective-C 中执行 alloc 和 init 在单独的语句中会导致对象根据 Xcode 静态分析器被释放?的主要内容,如果未能解决你的问题,请参考以下文章

Objective-C探究alloc方法的实现

从 Objective-C 里的 Alloc 和 AllocWithZone 谈起

《Objective-C 高级编程》 1.2.3节 alloc/retain/release/dealloc 实现——学习总结

Objective-C [[NSXMLDocument alloc] initWithContentsOfURL:url options:0 error:&error];帮助

为啥保留/释放而不是新/删除?

ObJective-C UIViewController生命周期及iOS程序执行顺序