【问题标题】:Why is Objective-C ARC deallocation dependent on whether an object was created in function?为什么 Objective-C ARC 释放依赖于对象是否是在函数中创建的?
【发布时间】:2013-05-04 21:46:07
【问题描述】:

我正在学习 ARC 内存管理,但遇到了一些对我来说没有意义的东西。

在下面的示例代码中,在 main() 中本地分配的对象在其指针分配给 nil 时被释放,正如我所期望的那样。

但是,如果在另一个函数中分配了相同类型的对象,并且在 main() 中定义了指向它的指针,并且该指针设置为 nil,则该对象在 main 函数退出之前不会被释放。这对我来说很神秘。

在下面的代码中,创建了 GRMemoryChecker 类的两个实例。在一种情况下,它直接在 main() 中分配,而在另一种情况下,main() 调用 itemMakerFunc() 进行分配。当您运行 main() 时,日志输出显示在函数中分配的实例在其指针设置为 nil 时并未解除分配——在函数退出时已解除分配。

我假设这意味着 itemMakerFunc() 创建的实例在指针设置为 nil 之前有 2 个所有者,而本地创建的实例只有 1 个。

但是为什么呢?在指针设置为 nil 时,还有哪些其他所有者存在?或者如果没有其他所有者仍然存在,为什么当它不存在时计数器没有递减?

GRMemoryChecker.h:

#import <Foundation/Foundation.h>

@interface GRMemoryChecker : NSObject
{
    NSString *name;
}
- (id)initWithName:(NSString *)str;

- (void)setName:(NSString *)str;

- (void) dealloc;
@end

GRMemoryChecker.m:

#import "GRMemoryChecker.h"

@implementation GRMemoryChecker

- (id)initWithName:(NSString *)str
{
    self = [super init];
    if (self)
    {
        [self setName:str];
    }
    return self;
}

- (void)setName:(NSString *)str
{
    name = str;
}

- (NSString *)description
{
    return name;
}

- (void) dealloc;
{
    NSLog(@"Destroyed: %@", self);
}
@end

ma​​in.m:

#import <Foundation/Foundation.h>
#import "GRMemoryChecker.h"

GRMemoryChecker *itemMakerFunc()
{
    return [[GRMemoryChecker alloc] initWithName:@"func-based checker" ];
}

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        GRMemoryChecker *checkerLocallyCreated = [[GRMemoryChecker alloc] initWithName:@"locally-created checker"];
        GRMemoryChecker *checkerFuncBased = itemMakerFunc();

        NSLog(@"Before setting func-based checker pointer to nil");
        checkerFuncBased = nil;
        NSLog(@"After setting func-based checker pointer to nil");

        NSLog(@"Before setting locally-created checker pointer to nil");
        checkerLocallyCreated = nil;
        NSLog(@"After setting locally-created checker pointer to nil");
    }
    return 0;
}

控制台输出:

Before setting func-based checker pointer to nil
After setting func-based checker pointer to nil
Before setting locally-created checker pointer to nil
Destroyed: locally-created checker
After setting locally-created checker pointer to nil
Destroyed: func-based checker

【问题讨论】:

  • 将优化器设置为-Os 再试一次,我怀疑您会看到您期望的行为..

标签: objective-c automatic-ref-counting


【解决方案1】:

它不取决于对象是否在函数中创建,但如果 函数具有“保留返回值”或“未保留返回值”(请参阅​​Objective-C Automatic Reference Counting)。

默认情况下,只有alloccopyinitmutableCopynew 系列中的方法 具有保留的返回值,这意味着该函数返回一个 (+1) 保留对象。

所有其他函数都有一个未保留的返回值。简化一点,你可以说 这些函数返回一个自动释放的值(受优化 由 ARC 编译器生成)以确保返回值在调用函数中有效。

当前自动释放池被销毁时,自动释放的值被释放。

您可以使用NS_RETURNS_RETAINED 属性更改函数的行为:

GRMemoryChecker *itemMakerFunc() NS_RETURNS_RETAINED;
GRMemoryChecker *itemMakerFunc()
{
    return [[GRMemoryChecker alloc] initWithName:@"func-based checker" ];
}

现在函数返回一个保留值,该值已被释放 当您通过设置checkerFuncBased = nil 删除强引用时立即:

在将基于 func 的检查器指针设置为 nil 之前 销毁:基于函数的检查器 将基于 func 的检查器指针设置为 nil 后 在将本地创建的检查器指针设置为 nil 之前 已销毁:本地创建的检查器 将本地创建的检查器指针设置为 nil 后

【讨论】:

  • 几乎;在 ARC 下,即使在未保留的情况下,通过一点编译器+运行时魔法从 ARC'd 代码调用非 ARC'd(或 ARC'd)代码时,也有可能避免自动释放池。请注意,自动释放行为会根据您在许多情况下是否没有优化或 -Os 而发生变化。函数是否“保留返回”与 OP 的情况 无关(但在其他方面相关且有趣);在单个 ARC 编译单元中,一旦优化器启动,对象将不会自动释放(我很确定)。
  • @bbum:你当然是对的。我只是试图让答案不要太复杂,并说“稍微简化一下”“受ARC编译器的优化”。 - 是的,使用“发布”构建的快速测试表明该对象没有被放入自动释放池并立即释放。
【解决方案2】:

ARC 强制执行您在手动保留和释放对象时应该遵循的相同内存管理语义。因此,让我们看看如何在 MRR 下编写它,看看它是否能告诉我们什么:

GRMemoryChecker *itemMakerFunc()
{
    return [[[GRMemoryChecker alloc] initWithName:@"func-based checker" ] autorelease];
}

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        GRMemoryChecker *checkerLocallyCreated = [[GRMemoryChecker alloc] initWithName:@"locally-created checker"];
        GRMemoryChecker *checkerFuncBased = itemMakerFunc();

        NSLog(@"Before setting func-based checker pointer to nil");
        checkerFuncBased = nil;
        NSLog(@"After setting func-based checker pointer to nil");

        NSLog(@"Before setting locally-created checker pointer to nil");
        [checkerLocallyCreated release];
        checkerLocallyCreated = nil;
        NSLog(@"After setting locally-created checker pointer to nil");
    }
    return 0;
}

所以基本上,alloc 返回的引用是一个拥有引用,需要释放。当一个对象只存在于一个函数中时,我们可以直接释放它,它会立即被释放。但是当我们从函数中返回它时,我们不能简单地释放它,因为它需要在 return 语句之后存在,所以它会被自动释放。

【讨论】:

  • 谢谢。虽然 Martin R 的回答也很有帮助(尤其是与 bbum 的评论配对时),但您的回答给了我我正在寻找的核心见解:“ARC 强制执行您在手动保留和释放时应该遵循的相同内存管理语义对象。”
【解决方案3】:

首先,您的 dealloc 方法正在泄漏。你必须在其中调用 [super dealloc] 否则对象永远不会被释放。

关于两个实例的不同行为,当您在 itemMakerFunc 中返回分配的对象时,ARC 将在返回时应用“自动释放”。因此,该实例是自动释放的,但在其他保留者释放它时不会立即释放。另一方面,另一个实例被保留,然后在将 nil 分配给变量时立即释放。认为自动释放是一个后置动作。它会在当前循环结束时释放,但不会立即释放。

【讨论】:

  • 启用了 ARC 的 -dealloc 不允许调用 [super dealloc]
  • 编译器为你插入[super dealloc]。
  • ARC 禁止“dealloc”的显式消息发送
猜你喜欢
  • 2012-08-27
  • 2011-12-13
  • 1970-01-01
  • 2015-07-24
  • 2011-04-17
  • 2011-03-16
  • 2012-11-21
  • 2011-11-09
  • 2011-01-15
相关资源
最近更新 更多